diff --git a/.changeset/bitter-clocks-sleep.md b/.changeset/bitter-clocks-sleep.md new file mode 100644 index 000000000000..04f32385a4b5 --- /dev/null +++ b/.changeset/bitter-clocks-sleep.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/container-definitions": major +--- + +Removed `request(...)` and `IFluidRouter` from `IContainer` + +The `request(...)` method and `IFluidRouter` property have been removed from `IContainer`. Please use the `IContainer.getEntryPoint()` method to get the container's entry point. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/bumpy-papayas-invite.md b/.changeset/bumpy-papayas-invite.md new file mode 100644 index 000000000000..839617553fe9 --- /dev/null +++ b/.changeset/bumpy-papayas-invite.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/aqueduct": major +--- + +Removed `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` + +The `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` helper methods have been removed from `"@fluidframework/aqueduct"`. Please move all code usage to the new `entryPoint` pattern. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/cold-mails-notice.md b/.changeset/cold-mails-notice.md new file mode 100644 index 000000000000..643024509402 --- /dev/null +++ b/.changeset/cold-mails-notice.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/matrix": major +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +--- + +Enable noImplicitAny in merge-tree, sequence, and matrix. This changes the return types of some functions from any to void. This does not represent a logic change and only serves to make the typing of these functions more accurate. diff --git a/.changeset/copied-llamas-digest.md b/.changeset/copied-llamas-digest.md new file mode 100644 index 000000000000..46065b803793 --- /dev/null +++ b/.changeset/copied-llamas-digest.md @@ -0,0 +1,8 @@ +--- +"@fluidframework/location-redirection-utils": major +--- + +Removes the @fluidframework/location-redirection-utils package + +The package was deprecated in version `2.0.0-internal.7.2.0`, and all of its library exports were moved to other packages. +It is now removed, and no future versions will be published. diff --git a/.changeset/cute-points-obey.md b/.changeset/cute-points-obey.md new file mode 100644 index 000000000000..5c5de5483f44 --- /dev/null +++ b/.changeset/cute-points-obey.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/container-runtime-definitions": major +"@fluidframework/runtime-definitions": major +--- + +Removed `resolveHandle` and `IFluidHandleContext` from ContainerRuntime interfaces + +The `IContainerRuntime.resolveHandle(...)` method and the `IContainerRuntimeBase.IFluidHandleContext` property have been removed. Please remove all usage of these APIs. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/dark-dancers-hug.md b/.changeset/dark-dancers-hug.md new file mode 100644 index 000000000000..cf212e696f11 --- /dev/null +++ b/.changeset/dark-dancers-hug.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/datastore-definitions": major +--- + +Removed `request` and `IFluidRouter` from `IFluidDataStoreRuntime` + +The `request` method and `IFluidRouter` property have been removed from `IFluidDataStoreRuntime`. Please migrate all usage to the `IFluidDataStoreRuntime.entryPoint` API. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/eager-teeth-accept.md b/.changeset/eager-teeth-accept.md new file mode 100644 index 000000000000..87bac9e49d89 --- /dev/null +++ b/.changeset/eager-teeth-accept.md @@ -0,0 +1,11 @@ +--- +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +"@fluidframework/undo-redo": major +--- + +Add experimental support for the obliterate operation + +This change adds experimental support for obliterate, a form of remove that deletes concurrently inserted segments. To use: enable the `mergeTreeEnableObliterate` feature flag and call the new `obliterateRange` functions. + +Note for `sequence` users: this change may cause compilation errors for those attaching event listeners. As long as obliterate isn't used in current handlers, their current implementation is sound. diff --git a/.changeset/easy-deer-retire.md b/.changeset/easy-deer-retire.md new file mode 100644 index 000000000000..951956f7e5fb --- /dev/null +++ b/.changeset/easy-deer-retire.md @@ -0,0 +1,20 @@ +--- +"@fluidframework/datastore-definitions": major +"@fluid-experimental/devtools-core": major +"@fluidframework/map": major +"@fluidframework/matrix": major +"@fluidframework/sequence": major +"@fluidframework/shared-summary-block": major +"@fluid-experimental/sharejs-json1": major +"@fluid-private/test-end-to-end-tests": major +"@fluidframework/test-runtime-utils": major +"@fluid-experimental/tree2": major +--- + +Update `Jsonable` and `Serializable` types from @fluidframework-definitions to require a generic parameter and if that type is `any` or `unknown` use a new result `JsonableTypeWith<>` that more accurately represents the limitation of serialization. Additional modifications: + +- `Jsonable`'s `TReplacement` parameter default has also been changed from `void` to `never`, which now disallows `void`. +- Unrecognized primitive types like `symbol` are now filtered to `never` instead of `{}`. +- Recursive types with arrays (`[]`) are now supported. + +`Serializable` is commonly used for DDS values and now requires more precision when using them. For example SharedMatrix (unqualified) has an `any` default that meant values were `Serializable` (i.e. `any`), but now `Serializable` is `JsonableTypeWith` which may be problematic for reading or writing. Preferred correction is to specify the value type but casting through `any` may provide a quick fix. diff --git a/.changeset/every-suns-bow.md b/.changeset/every-suns-bow.md new file mode 100644 index 000000000000..f618e52ef5f2 --- /dev/null +++ b/.changeset/every-suns-bow.md @@ -0,0 +1,8 @@ +--- +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +--- + +Remove Marker.hasSimpleType and make sequence operations return void + +Marker.hasSimpleType was unused. Sequence operations now no longer return IMergeTree\*Msg types. These types are redundant with the input. diff --git a/.changeset/floppy-mails-hammer.md b/.changeset/floppy-mails-hammer.md new file mode 100644 index 000000000000..00fdaf621a15 --- /dev/null +++ b/.changeset/floppy-mails-hammer.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/container-definitions": major +"@fluidframework/container-loader": major +--- + +Removed `request(...)` and `IFluidRouter` from `ILoader` and `Loader` + +The `request(...)` method and `IFluidRouter` property have been removed from `ILoader` and `Loader`. Instead, after calling `ILoader.resolve(...)`, call the `getEntryPoint()` method on the returned `IContainer`. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/fuzzy-cities-wonder.md b/.changeset/fuzzy-cities-wonder.md new file mode 100644 index 000000000000..abdcec04fe55 --- /dev/null +++ b/.changeset/fuzzy-cities-wonder.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/datastore": major +--- + +Removed `FluidDataStoreRuntime.load(...)` + +The static method `FluidDataStoreRuntime.load(...)` has been removed. Please migrate all usage of this method to `FluidDataStoreRuntime` constructor. diff --git a/.changeset/gentle-hats-appear.md b/.changeset/gentle-hats-appear.md new file mode 100644 index 000000000000..a1109d6de61e --- /dev/null +++ b/.changeset/gentle-hats-appear.md @@ -0,0 +1,17 @@ +--- +"@fluidframework/aqueduct": major +"@fluidframework/data-object-base": major +"@fluidframework/test-utils": major +--- + +Removed `IFluidRouter` from DataObject interfaces and classes + +The `IFluidRouter` property has been removed from a number of DataObject related classes: + +- `PureDataObject` +- `LazyLoadedDataObject` +- `TestFluidObject` + +Please migrate to the new `entryPoint` pattern or use the relevant `request` method as necessary. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/gold-cats-tell.md b/.changeset/gold-cats-tell.md new file mode 100644 index 000000000000..fa914b00142b --- /dev/null +++ b/.changeset/gold-cats-tell.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/local-driver": major +--- + +LocalDocumentStorageService class property type changes + +The `repositoryUrl` property on the `LocalDocumentStorageService` class has changed from a property getter to a +`readonly` field. While this is an API change, there should be no changes required on the consumer side since calling +code should remain the same. diff --git a/.changeset/green-parrots-poke.md b/.changeset/green-parrots-poke.md new file mode 100644 index 000000000000..176cfa67f715 --- /dev/null +++ b/.changeset/green-parrots-poke.md @@ -0,0 +1,12 @@ +--- +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +--- + +Remove public exports from merge-tree and sequence + +Removes or marks `@internal` BaseSegment.ack, Client, CollaborationWindow, compareNumbers, compareStrings, createAnnotateMarkerOp, createAnnotateRangeOp, createGroupOp, createInsertOp, createInsertSegmentOp, createRemoveRangeOp, IConsensusInfo, IConsensusValue, IMarkerModifiedAction, IMergeTreeTextHelper, LocalClientId, MergeTreeDeltaCallback, MergeTreeMaintenanceCallback, NonCollabClient, SegmentAccumulator, SegmentGroup, SegmentGroupCollection.enqueue, SegmentGroupCollection.dequeue, SegmentGroupCollection.pop, SortedSegmentSet, SortedSegmentSetItem, SortedSet, toRemovalInfo, TreeMaintenanceSequenceNumber, and UniversalSequenceNumber from merge-tree. + +Removes SharedSegmentSequence.submitSequenceMessage from sequence. + +This functionality was never intended for public export. diff --git a/.changeset/little-mirrors-carry.md b/.changeset/little-mirrors-carry.md new file mode 100644 index 000000000000..f21809c6831b --- /dev/null +++ b/.changeset/little-mirrors-carry.md @@ -0,0 +1,8 @@ +--- +"@fluidframework/container-definitions": major +"@fluidframework/container-runtime": major +--- + +Remove Pending State Related Deprecations + +This change removes the deprecated `IPendingLocalState` and `IRuntiume.notifyAttaching`. There is no replacement as they are not longer used. diff --git a/.changeset/lucky-worms-return.md b/.changeset/lucky-worms-return.md new file mode 100644 index 000000000000..dc4f15f424f6 --- /dev/null +++ b/.changeset/lucky-worms-return.md @@ -0,0 +1,17 @@ +--- +"@fluidframework/container-definitions": major +"@fluidframework/container-runtime": major +"@fluidframework/runtime-definitions": major +--- + +Removed request pattern from `ContainerRuntime`, `IRuntime`, and `IContainerRuntimeBase` + +The `request(...)` method and `IFluidRouter` property have been removed from the following places: + +- `ContainerRuntime` +- `IRuntime` +- `IContainerRuntimeBase` + +Please use the `IRuntime.getEntryPoint()` method to get the runtime's entry point. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/metal-geese-battle.md b/.changeset/metal-geese-battle.md new file mode 100644 index 000000000000..71773d4b8f2a --- /dev/null +++ b/.changeset/metal-geese-battle.md @@ -0,0 +1,8 @@ +--- +"@fluid-experimental/attributor": major +"@fluidframework/container-runtime": major +--- + +Removed `ContainerRuntime.load(...)` + +The static method `ContainerRuntime.load(...)` has been removed. Please migrate all usage of this method to `ContainerRuntime.loadRuntime(...)`. diff --git a/.changeset/nice-worms-try.md b/.changeset/nice-worms-try.md new file mode 100644 index 000000000000..5e6c8758c2d3 --- /dev/null +++ b/.changeset/nice-worms-try.md @@ -0,0 +1,11 @@ +--- +"@fluidframework/container-runtime": major +"@fluidframework/container-runtime-definitions": major +"@fluidframework/core-interfaces": major +--- + +Removed `getRootDataStore` + +The `getRootDataStore` method has been removed from `IContainerRuntime` and `ContainerRuntime`. Please migrate all usage to the new `getAliasedDataStoreEntryPoint` method. This method returns the data store's entry point which is its `IFluidHandle`. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/nine-coins-send.md b/.changeset/nine-coins-send.md new file mode 100644 index 000000000000..c74b18b84345 --- /dev/null +++ b/.changeset/nine-coins-send.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/aqueduct": major +--- + +Removed `IRootDataObjectFactory` + +The `IRootDataObjectFactory` interface has been removed. Please remove all usage of it. diff --git a/.changeset/open-women-yawn.md b/.changeset/open-women-yawn.md new file mode 100644 index 000000000000..3e4cf41a0cd8 --- /dev/null +++ b/.changeset/open-women-yawn.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/container-definitions": major +"@fluidframework/container-loader": major +"@fluidframework/datastore": major +"@fluidframework/runtime-utils": major +--- + +Fix ISnapshotTreeWithBlobContents and mark internal + +ISnapshotTreeWithBlobContents is an internal type that should not be used externally. Additionally, the type didn't match the usage, specifically in runtime-utils where an `any` cast was used to work around undefined blobContents. The type has been updated to reflect that blobContents can be undefined diff --git a/.changeset/poor-peas-bow.md b/.changeset/poor-peas-bow.md new file mode 100644 index 000000000000..961b0877f08f --- /dev/null +++ b/.changeset/poor-peas-bow.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/merge-tree": major +--- + +Remove IIntegerRange + +This interface is deprecated and was not intended for public export. diff --git a/.changeset/shaggy-doodles-fetch.md b/.changeset/shaggy-doodles-fetch.md new file mode 100644 index 000000000000..a25f79060447 --- /dev/null +++ b/.changeset/shaggy-doodles-fetch.md @@ -0,0 +1,10 @@ +--- +"@fluidframework/datastore": major +"@fluidframework/runtime-definitions": major +--- + +Removed `IFluidRouter` from `IFluidDataStoreChannel` and `FluidDataStoreRuntime` + +The `IFluidRouter` property has been removed from `IFluidDataStoreChannel` and `FluidDataStoreRuntime`. Please migrate all usage to the `IFluidDataStoreChannel.entryPoint` API. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/shaky-socks-fail.md b/.changeset/shaky-socks-fail.md new file mode 100644 index 000000000000..4f376fa3013f --- /dev/null +++ b/.changeset/shaky-socks-fail.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/runtime-utils": major +--- + +Removed `requestFluidObject` + +The `requestFluidObject` utility has been removed. Please migrate all usage of it to the new `entryPoint` pattern. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/silent-coins-report.md b/.changeset/silent-coins-report.md new file mode 100644 index 000000000000..e0744a415ff3 --- /dev/null +++ b/.changeset/silent-coins-report.md @@ -0,0 +1,12 @@ +--- +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +--- + +Remove support for combining ops + +In `merge-tree`, removes ICombiningOp; the combiningOp field from IMergeTreeAnnotateMsg; the op argument from BaseSegment.addProperties, PropertiesManager.addProperties, and ReferencePosition.addProperties; and the enum variant PropertiesRollback.Rewrite. + +In `sequence`, removes the combiningOp argument from SharedSegmentSequence.annotateRange and SharedString.annotateMarker and the function SharedString.annotateMarkerNotifyConsensus. + +This functionality was largely unused and had no test coverage. diff --git a/.changeset/sixty-kings-carry.md b/.changeset/sixty-kings-carry.md new file mode 100644 index 000000000000..ad2beb96ef53 --- /dev/null +++ b/.changeset/sixty-kings-carry.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/test-runtime-utils": major +--- + +MockDeltaManager class property type changes + +The `active` and `maxMessageSize` properties on the `MockDeltaManager` class have changed from property getters to +`readonly` fields. While this is an API change, there should be no changes required on the consumer side since calling +code should remain the same. diff --git a/.changeset/sweet-pandas-chew.md b/.changeset/sweet-pandas-chew.md new file mode 100644 index 000000000000..be30ea06a6b3 --- /dev/null +++ b/.changeset/sweet-pandas-chew.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/core-interfaces": major +--- + +Removed `IFluidRouter` and `IProvideFluidRouter` + +The `IFluidRouter` and `IProvideFluidRouter` interfaces have been removed. Please migrate all usage to the new `entryPoint` pattern. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/tangy-beers-pump.md b/.changeset/tangy-beers-pump.md new file mode 100644 index 000000000000..42b13989c3e9 --- /dev/null +++ b/.changeset/tangy-beers-pump.md @@ -0,0 +1,8 @@ +--- +"@fluidframework/merge-tree": major +"@fluidframework/sequence": major +--- + +Remove Client.getStackContext, SharedSegmentSequence.getStackContext, IntervalType.Nest, ReferenceType.NestBegin, ReferenceType.NestEnd, internedSpaces, RangeStackMap, refGetRangeLabels, refHasRangeLabel, and refHasRangeLabels + +This functionality is deprecated, has low test coverage, and is largely unused. diff --git a/.changeset/tangy-geckos-swim.md b/.changeset/tangy-geckos-swim.md new file mode 100644 index 000000000000..8abecaf43e91 --- /dev/null +++ b/.changeset/tangy-geckos-swim.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/container-loader": major +--- + +Removed `requestResolvedObjectFromContainer` + +The helper function `requestResolvedObjectFromContainer` has been removed. Please remove all calls to it and instead use the new `entryPoint` pattern. See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/tender-planets-beg.md b/.changeset/tender-planets-beg.md new file mode 100644 index 000000000000..e5abfbdb529a --- /dev/null +++ b/.changeset/tender-planets-beg.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/runtime-definitions": major +--- + +Removed `request` and `IFluidRouter` from `IDataStore` + +The `request` method and `IFluidRouter` property have been removed from `IDataStore`. Please migrate all usage to the `IDataStore.entryPoint` API. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/thick-ads-marry.md b/.changeset/thick-ads-marry.md new file mode 100644 index 000000000000..61cd262f17a6 --- /dev/null +++ b/.changeset/thick-ads-marry.md @@ -0,0 +1,22 @@ +--- +"@fluid-example/example-utils": major +"@fluidframework/aqueduct": major +"@fluidframework/request-handler": major +--- + +Removed `requestHandler` utilities + +The following `requestHandler` utilities have been removed: + +- `makeModelRequestHandler` +- `defaultFluidObjectRequestHandler` +- `defaultRouteRequestHandler` +- `mountableViewRequestHandler` +- `createFluidObjectResponse` +- `rootDataStoreRequestHandler` +- `handleFromLegacyUri` +- `RuntimeRequestHandlerBuilder` + +Please migrate all usage to the new `entryPoint` pattern. + +See [Removing-IFluidRouter.md](https://github.com/microsoft/FluidFramework/blob/main/packages/common/core-interfaces/Removing-IFluidRouter.md) for more details. diff --git a/.changeset/wet-bags-pick.md b/.changeset/wet-bags-pick.md new file mode 100644 index 000000000000..57f8eca0da98 --- /dev/null +++ b/.changeset/wet-bags-pick.md @@ -0,0 +1,7 @@ +--- +"@fluidframework/merge-tree": major +--- + +Remove Stack, clone, combine, createMap, extend, extendIfUndefined, and matchProperties + +This functionality is deprecated and was not intended for public export. diff --git a/.changeset/wet-hornets-trade.md b/.changeset/wet-hornets-trade.md new file mode 100644 index 000000000000..46b1de16ddcb --- /dev/null +++ b/.changeset/wet-hornets-trade.md @@ -0,0 +1,17 @@ +--- +"@fluidframework/azure-client": major +"fluid-framework": major +"@fluidframework/fluid-static": major +"@fluidframework/tinylicious-client": major +--- + +Removes Deprecated FluidStatic Classes + +This change removes a number of deprecated and unnecessarily exposed FluidStatic classes. +The removed classes are as follows: + +- AzureAudience +- TinyliciousAudience +- DOProviderContainerRuntimeFactory +- FluidContainer +- ServiceAudience diff --git a/PACKAGES.md b/PACKAGES.md index b50c13b8a843..525733ffa2a7 100644 --- a/PACKAGES.md +++ b/PACKAGES.md @@ -99,7 +99,7 @@ The dependencies between layers are enforced by the layer-check command._ | Packages | Layer Dependencies | | --- | --- | -| - [@fluidframework/container-loader](/packages/loader/container-loader)
- [@fluidframework/location-redirection-utils](/packages/loader/location-redirection-utils)
- [@fluid-private/test-loader-utils](/packages/loader/test-loader-utils)
 
 
 
 
 
  | - [Core-Interfaces](#Core-Interfaces)
- [Protocol-Definitions](#Protocol-Definitions)
- [Driver-Definitions](#Driver-Definitions)
- [Container-Definitions](#Container-Definitions)
- [Core-Utils](#Core-Utils)
- [Client-Utils](#Client-Utils)
- [Protocol-Utils](#Protocol-Utils)
- [Telemetry-Utils](#Telemetry-Utils)
- [Driver-Utils](#Driver-Utils) | +| - [@fluidframework/container-loader](/packages/loader/container-loader)
- [@fluid-private/test-loader-utils](/packages/loader/test-loader-utils)
 
 
 
 
 
  | - [Core-Interfaces](#Core-Interfaces)
- [Protocol-Definitions](#Protocol-Definitions)
- [Driver-Definitions](#Driver-Definitions)
- [Container-Definitions](#Container-Definitions)
- [Core-Utils](#Core-Utils)
- [Client-Utils](#Client-Utils)
- [Protocol-Utils](#Protocol-Utils)
- [Telemetry-Utils](#Telemetry-Utils)
- [Driver-Utils](#Driver-Utils) | ### Runtime @@ -220,4 +220,3 @@ The dependencies between layers are enforced by the layer-check command._ | Packages | Layer Dependencies | | --- | --- | | - [@fluidframework/server-routerlicious](/server/routerlicious/packages/routerlicious)
 
 
 
  | - [Common-Definitions](#Common-Definitions)
- [Protocol-Definitions](#Protocol-Definitions)
- [Common-Utils](#Common-Utils)
- [Server-Shared-Utils](#Server-Shared-Utils)
- [Server-Libs](#Server-Libs) | - diff --git a/README.md b/README.md index 16c7c2218f7d..71a4abc2644c 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ Dependencies between packages in various layers of the system are enforced via a [layer-check](./build-tools/packages/build-tools/src/layerCheck). You can view the full list of packages and layers in [PACKAGES.md](./PACKAGES.md). +- Note: to update the contents of `PACKAGES.md` for local package changes, run `pnpm layer-check --md .`. + ## Setup and Building Install the required tools: diff --git a/azure/packages/azure-client/api-report/azure-client.api.md b/azure/packages/azure-client/api-report/azure-client.api.md index 50b2426279a9..fa8c86af9502 100644 --- a/azure/packages/azure-client/api-report/azure-client.api.md +++ b/azure/packages/azure-client/api-report/azure-client.api.md @@ -5,7 +5,6 @@ ```ts import { ContainerSchema } from '@fluidframework/fluid-static'; -import { IClient } from '@fluidframework/protocol-definitions'; import { ICompressionStorageConfig } from '@fluidframework/driver-utils'; import { IConfigProviderBase } from '@fluidframework/core-interfaces'; import { IFluidContainer } from '@fluidframework/fluid-static'; @@ -18,12 +17,6 @@ import { ITokenProvider } from '@fluidframework/routerlicious-driver'; import { ITokenResponse } from '@fluidframework/routerlicious-driver'; import { IUser } from '@fluidframework/protocol-definitions'; import { ScopeType } from '@fluidframework/protocol-definitions'; -import { ServiceAudience } from '@fluidframework/fluid-static'; - -// @internal @deprecated -export class AzureAudience extends ServiceAudience implements IAzureAudience { - protected createServiceMember(audienceMember: IClient): AzureMember; -} // @alpha export class AzureClient { diff --git a/azure/packages/azure-client/package.json b/azure/packages/azure-client/package.json index d83de2ca2352..1a17d9dee2d8 100644 --- a/azure/packages/azure-client/package.json +++ b/azure/packages/azure-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-client", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A tool to enable creation and loading of Fluid containers using the Azure Fluid Relay service", "homepage": "https://fluidframework.com", "repository": { @@ -115,6 +115,11 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedClassDeclaration_AzureAudience": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/azure/packages/azure-client/src/AzureAudience.ts b/azure/packages/azure-client/src/AzureAudience.ts index ededa3a385fd..65e47c1c9713 100644 --- a/azure/packages/azure-client/src/AzureAudience.ts +++ b/azure/packages/azure-client/src/AzureAudience.ts @@ -3,30 +3,9 @@ * Licensed under the MIT License. */ import { assert } from "@fluidframework/core-utils"; -import { ServiceAudience } from "@fluidframework/fluid-static"; import { type IClient } from "@fluidframework/protocol-definitions"; -import { type AzureMember, type AzureUser, type IAzureAudience } from "./interfaces"; - -/** - * Azure-specific {@link @fluidframework/fluid-static#ServiceAudience} implementation. - * - * @remarks Operates in terms of {@link AzureMember}s. - * @deprecated This class will be removed. use {@link IAzureAudience} instead - * @internal - */ -export class AzureAudience extends ServiceAudience implements IAzureAudience { - /** - * Creates a {@link @fluidframework/fluid-static#ServiceAudience} from the provided - * {@link @fluidframework/protocol-definitions#IClient | audience member}. - * - * @param audienceMember - Audience member for which the `ServiceAudience` will be generated. - * Note: its {@link @fluidframework/protocol-definitions#IClient.user} is required to be an {@link AzureUser}. - */ - protected createServiceMember(audienceMember: IClient): AzureMember { - return createAzureAudienceMember(audienceMember); - } -} +import { type AzureMember, type AzureUser } from "./interfaces"; /** * Creates Azure-specific audience member diff --git a/azure/packages/azure-client/src/AzureClient.ts b/azure/packages/azure-client/src/AzureClient.ts index a643af74c4e2..2ccefd6b8a34 100644 --- a/azure/packages/azure-client/src/AzureClient.ts +++ b/azure/packages/azure-client/src/AzureClient.ts @@ -305,13 +305,11 @@ export class AzureClient { } private async getContainerEntryPoint(container: IContainer): Promise { - const rootDataObject: FluidObject | undefined = - await container.getEntryPoint(); - assert(rootDataObject !== undefined, "entryPoint must exist"); - // ! This "if" is needed for back-compat (older instances of IRootDataObject may not have the IRootDataObject property) - if (rootDataObject.IRootDataObject === undefined) { - return rootDataObject as IRootDataObject; - } + const rootDataObject: FluidObject = await container.getEntryPoint(); + assert( + rootDataObject.IRootDataObject !== undefined, + "entryPoint must be of type IRootDataObject", + ); return rootDataObject.IRootDataObject; } // #endregion diff --git a/azure/packages/azure-client/src/index.ts b/azure/packages/azure-client/src/index.ts index b999ccfc3bd9..364f2717e17a 100644 --- a/azure/packages/azure-client/src/index.ts +++ b/azure/packages/azure-client/src/index.ts @@ -9,7 +9,6 @@ * @packageDocumentation */ -export { AzureAudience } from "./AzureAudience"; export { AzureClient } from "./AzureClient"; export { AzureFunctionTokenProvider } from "./AzureFunctionTokenProvider"; export type { diff --git a/azure/packages/azure-client/src/test/types/validateAzureClientPrevious.generated.ts b/azure/packages/azure-client/src/test/types/validateAzureClientPrevious.generated.ts index 1af739acc843..c8b18b3fbe30 100644 --- a/azure/packages/azure-client/src/test/types/validateAzureClientPrevious.generated.ts +++ b/azure/packages/azure-client/src/test/types/validateAzureClientPrevious.generated.ts @@ -24,26 +24,14 @@ type TypeOnly = T extends number /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_AzureAudience": {"forwardCompat": false} +* "RemovedClassDeclaration_AzureAudience": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_AzureAudience(): - TypeOnly; -declare function use_current_ClassDeclaration_AzureAudience( - use: TypeOnly): void; -use_current_ClassDeclaration_AzureAudience( - get_old_ClassDeclaration_AzureAudience()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_AzureAudience": {"backCompat": false} +* "RemovedClassDeclaration_AzureAudience": {"backCompat": false} */ -declare function get_current_ClassDeclaration_AzureAudience(): - TypeOnly; -declare function use_old_ClassDeclaration_AzureAudience( - use: TypeOnly): void; -use_old_ClassDeclaration_AzureAudience( - get_current_ClassDeclaration_AzureAudience()); /* * Validate forward compat by using old type in place of current type diff --git a/azure/packages/azure-local-service/package.json b/azure/packages/azure-local-service/package.json index 98239aec3a17..fcc135958412 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Local implementation of the Azure Fluid Relay service for testing/development use", "homepage": "https://fluidframework.com", "repository": { diff --git a/azure/packages/azure-service-utils/package.json b/azure/packages/azure-service-utils/package.json index 2273ae9f47e5..8f95bfe26786 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Helper service-side utilities for connecting to Azure Fluid Relay service", "homepage": "https://fluidframework.com", "repository": { diff --git a/azure/packages/external-controller/package.json b/azure/packages/external-controller/package.json index 4d4df4671214..8c474b111ea8 100644 --- a/azure/packages/external-controller/package.json +++ b/azure/packages/external-controller/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-external-controller", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", diff --git a/azure/packages/external-controller/tests/index.ts b/azure/packages/external-controller/tests/index.ts index 5a4cd5049e55..73701395b217 100644 --- a/azure/packages/external-controller/tests/index.ts +++ b/azure/packages/external-controller/tests/index.ts @@ -12,7 +12,6 @@ import { IRuntimeFactory, } from "@fluidframework/container-definitions"; import { Loader } from "@fluidframework/container-loader"; -import { DOProviderContainerRuntimeFactory, FluidContainer } from "@fluidframework/fluid-static"; import { LocalDocumentServiceFactory, LocalResolver, @@ -25,6 +24,10 @@ import { import { DiceRollerController } from "../src/controller"; import { makeAppView } from "../src/view"; +import { + IFluidContainer, + createDOProviderContainerRuntimeFactory, +} from "@fluidframework/fluid-static"; // Since this is a single page Fluid application we are generating a new document id // if one was not provided @@ -105,7 +108,7 @@ export const containerConfig = { }, }; -async function initializeNewContainer(container: FluidContainer): Promise { +async function initializeNewContainer(container: IFluidContainer): Promise { // We now get the first SharedMap from the container const sharedMap1 = container.initialObjects.map1 as SharedMap; const sharedMap2 = container.initialObjects.map2 as SharedMap; @@ -128,12 +131,12 @@ export async function createContainerAndRenderInElement( // to store ops. const { container, attach } = await getSessionStorageContainer( documentId, - new DOProviderContainerRuntimeFactory(containerConfig), + createDOProviderContainerRuntimeFactory({ schema: containerConfig }), createNewFlag, ); // Get the Default Object from the Container - const fluidContainer = (await container.getEntryPoint()) as FluidContainer; + const fluidContainer = (await container.getEntryPoint()) as IFluidContainer; if (createNewFlag) { await initializeNewContainer(fluidContainer); await attach?.(); diff --git a/azure/packages/test/end-to-end-tests/package.json b/azure/packages/test/end-to-end-tests/package.json index 7e84d88d2c92..7687b577a242 100644 --- a/azure/packages/test/end-to-end-tests/package.json +++ b/azure/packages/test/end-to-end-tests/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/azure-end-to-end-tests", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Azure client end to end tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/azure/packages/test/scenario-runner/package.json b/azure/packages/test/scenario-runner/package.json index dd30129546db..a425655e2d46 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Azure client end to end tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/azure/packages/test/scenario-runner/src/packageVersion.ts b/azure/packages/test/scenario-runner/src/packageVersion.ts index d38331e907a2..6045527b51d9 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/common/lib/common-utils/package.json b/common/lib/common-utils/package.json index d71590d60656..06cb8edf9c78 100644 --- a/common/lib/common-utils/package.json +++ b/common/lib/common-utils/package.json @@ -76,7 +76,7 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-definitions": "^1.0.0", + "@fluidframework/common-definitions": "^1.1.0-220319", "@types/events": "^3.0.0", "base64-js": "^1.5.1", "buffer": "^6.0.3", diff --git a/common/lib/common-utils/pnpm-lock.yaml b/common/lib/common-utils/pnpm-lock.yaml index 94add1a617a3..55ca42f79093 100644 --- a/common/lib/common-utils/pnpm-lock.yaml +++ b/common/lib/common-utils/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-definitions': ^1.0.0 + '@fluidframework/common-definitions': ^1.1.0-220319 '@fluidframework/common-utils-previous': npm:@fluidframework/common-utils@1.0.0 '@fluidframework/eslint-config-fluid': ^2.1.0 '@microsoft/api-extractor': ^7.38.3 @@ -53,7 +53,7 @@ importers: ts-node: ^10.9.1 typescript: ~4.5.5 dependencies: - '@fluidframework/common-definitions': 1.0.0 + '@fluidframework/common-definitions': 1.1.0-220319 '@types/events': 3.0.0 base64-js: 1.5.1 buffer: 6.0.3 @@ -712,8 +712,8 @@ packages: resolution: {integrity: sha512-KaoQ7w2MDH5OeRKVatL5yVOCFg+9wD6bLSLFh1/TV1EZM46l49iBqO7UVjUtPE6BIm0jvvOzJXULGVSpzokX3g==} dev: true - /@fluidframework/common-definitions/1.0.0: - resolution: {integrity: sha512-t0jm6u4RX77Fn3rnoxDmavzo26y/JBsNIz5iptnamBgUTOo37i7wXr9VPB8+AjCEd3kcXPyFEoNa8zIEvgOekQ==} + /@fluidframework/common-definitions/1.1.0-220319: + resolution: {integrity: sha512-jNs+MM3EoYBcm1dePQ8r+ybUFVVPfWHKqh/1xvD3+YTJrQMfl2BXWb8gzaT0tFEJk4M761fvNroB6zvvnV3irA==} dev: false /@fluidframework/common-utils/1.0.0: diff --git a/examples/apps/attributable-map/package.json b/examples/apps/attributable-map/package.json index a0fafe612097..b08a32d5ebf9 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a hit counter as a standalone app.", "homepage": "https://fluidframework.com", diff --git a/examples/apps/collaborative-textarea/package.json b/examples/apps/collaborative-textarea/package.json index 283492758a1a..8e01a641e87d 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "A minimal example using the react collaborative-textarea", "homepage": "https://fluidframework.com", diff --git a/examples/apps/contact-collection/package.json b/examples/apps/contact-collection/package.json index 216228dafbd2..77abfafe35d2 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Example of using a Fluid Object as a collection of items", "homepage": "https://fluidframework.com", diff --git a/examples/apps/data-object-grid/package.json b/examples/apps/data-object-grid/package.json index 17b5ec1094dd..e457927792eb 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.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", diff --git a/examples/apps/data-object-grid/src/dataObjectGrid.ts b/examples/apps/data-object-grid/src/dataObjectGrid.ts index d269a8a46bad..920f61a65935 100644 --- a/examples/apps/data-object-grid/src/dataObjectGrid.ts +++ b/examples/apps/data-object-grid/src/dataObjectGrid.ts @@ -62,7 +62,7 @@ export interface IDataObjectGridItem { /** * The unknown blob of data that backs the instance of the item. Probably contains handles, etc. */ - readonly serializableData: Serializable; + readonly serializableData: Serializable; /** * The react grid layout of the item. */ diff --git a/examples/apps/presence-tracker/package.json b/examples/apps/presence-tracker/package.json index fe9e9986a0e5..996643254b60 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Example Data Object that tracks page focus for Audience members using signals.", "homepage": "https://fluidframework.com", diff --git a/examples/apps/task-selection/package.json b/examples/apps/task-selection/package.json index 4e6772cb9a35..b07c4aa3a457 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Example demonstrating selecting a unique task amongst connected Fluid clients", "homepage": "https://fluidframework.com", diff --git a/examples/apps/tree-comparison/package.json b/examples/apps/tree-comparison/package.json index 03ba39e9244d..bad0a753b07a 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Comparing API usage in legacy SharedTree and new SharedTree.", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/baseline/package.json b/examples/benchmarks/bubblebench/baseline/package.json index e8ce1fe739e2..a9771ff6fa95 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/common/package.json b/examples/benchmarks/bubblebench/common/package.json index 10f77feb2920..a28a84c734b5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/common/src/types.ts b/examples/benchmarks/bubblebench/common/src/types.ts index a62dc84cd267..6630641058bb 100644 --- a/examples/benchmarks/bubblebench/common/src/types.ts +++ b/examples/benchmarks/bubblebench/common/src/types.ts @@ -30,7 +30,7 @@ export interface IBubble { export interface IClient { clientId: string; color: string; - bubbles: IArrayish; + bubbles: IBubble[]; } /** diff --git a/examples/benchmarks/bubblebench/editable-shared-tree/package.json b/examples/benchmarks/bubblebench/editable-shared-tree/package.json index a8325671f4dc..6e02edd941b4 100644 --- a/examples/benchmarks/bubblebench/editable-shared-tree/package.json +++ b/examples/benchmarks/bubblebench/editable-shared-tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-editable-shared-tree", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/ot/package.json b/examples/benchmarks/bubblebench/ot/package.json index d55cfe0cdf00..007ff579d78d 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/ot/src/main.ts b/examples/benchmarks/bubblebench/ot/src/main.ts index 05c4beb53943..9a23f10f536e 100644 --- a/examples/benchmarks/bubblebench/ot/src/main.ts +++ b/examples/benchmarks/bubblebench/ot/src/main.ts @@ -19,7 +19,10 @@ export class Bubblebench extends DataObject { protected async initializingFirstTime() { const tree = (this.maybeTree = SharedJson1.create(this.runtime)); - tree.replace([], tree.get(), { clients: [] }); + const initialTree = { clients: [] }; + // unknown used to workaround recursive Doc type that otherwise results in + // "Type instantiation is excessively deep and possibly infinite" error. + tree.replace([], tree.get(), initialTree); this.root.set("tree", this.maybeTree.handle); } diff --git a/examples/benchmarks/bubblebench/sharedtree/package.json b/examples/benchmarks/bubblebench/sharedtree/package.json index bd7d1c193ee9..872bed103190 100644 --- a/examples/benchmarks/bubblebench/sharedtree/package.json +++ b/examples/benchmarks/bubblebench/sharedtree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/bubblebench-sharedtree", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Bubblemark inspired DDS benchmark", "homepage": "https://fluidframework.com", diff --git a/examples/benchmarks/bubblebench/sharedtree/src/proxy/treeutils.ts b/examples/benchmarks/bubblebench/sharedtree/src/proxy/treeutils.ts index 51cb7260df73..1639720b0558 100644 --- a/examples/benchmarks/bubblebench/sharedtree/src/proxy/treeutils.ts +++ b/examples/benchmarks/bubblebench/sharedtree/src/proxy/treeutils.ts @@ -13,7 +13,10 @@ export const enum NodeKind { } // Helper for creating Scalar nodes in SharedTree -export const makeScalar = (idContext: NodeIdContext, value: Serializable): ChangeNode => ({ +export const makeScalar = ( + idContext: NodeIdContext, + value: Exclude, object>, +): ChangeNode => ({ identifier: idContext.generateNodeId(), definition: NodeKind.scalar as Definition, traits: {}, @@ -27,9 +30,7 @@ export function fromJson(idContext: NodeIdContext, value: Serializable): C identifier: idContext.generateNodeId(), definition: NodeKind.array as Definition, traits: { - items: value.map( - (property: Serializable): ChangeNode => fromJson(idContext, property), - ), + items: value.map((property): ChangeNode => fromJson(idContext, property)), }, }; } else if (value === null) { diff --git a/examples/benchmarks/bubblebench/sharedtree/src/state.ts b/examples/benchmarks/bubblebench/sharedtree/src/state.ts index f5224c147e25..29429c0a7dea 100644 --- a/examples/benchmarks/bubblebench/sharedtree/src/state.ts +++ b/examples/benchmarks/bubblebench/sharedtree/src/state.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import { Serializable } from "@fluidframework/datastore-definitions"; import { Change, SharedTree } from "@fluid-experimental/tree"; import { IAppState, @@ -48,7 +47,7 @@ export class AppState implements IAppState { this.root = TreeObjectProxy(this.tree, this.tree.currentView.root, this.update); const json = makeClient(_width, _height, numBubbles); - const clientNode = fromJson(tree, json as Serializable); + const clientNode = fromJson(tree, json); (this.clients as unknown as TreeArrayProxy).pushNode(clientNode); this.localClient = TreeObjectProxy(this.tree, clientNode.identifier, this.update); diff --git a/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json b/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json index f218a231aecb..81a702d4f317 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", diff --git a/examples/client-logger/app-insights-logger/package.json b/examples/client-logger/app-insights-logger/package.json index 18d0c6c5e5c4..458dcbe559f5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.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", diff --git a/examples/data-objects/canvas/package.json b/examples/data-objects/canvas/package.json index ce6582382241..3ded0aec4805 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Fluid ink canvas", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/clicker/package.json b/examples/data-objects/clicker/package.json index b9e81d6816bb..3ba80c41e6ba 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid component sample to implement a collaborative counter.", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/codemirror/package.json b/examples/data-objects/codemirror/package.json index a7f8102e7706..a17a23518a1f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", @@ -48,7 +48,6 @@ "@fluidframework/datastore": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", "@fluidframework/map": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", "@fluidframework/request-handler": "workspace:~", "@fluidframework/runtime-definitions": "workspace:~", "@fluidframework/runtime-utils": "workspace:~", diff --git a/examples/data-objects/codemirror/src/codeMirror.ts b/examples/data-objects/codemirror/src/codeMirror.ts index 82b4fd7fae06..14c0d4c11fc3 100644 --- a/examples/data-objects/codemirror/src/codeMirror.ts +++ b/examples/data-objects/codemirror/src/codeMirror.ts @@ -11,13 +11,12 @@ import { mixinRequestHandler, } from "@fluidframework/datastore"; import { ISharedMap, SharedMap } from "@fluidframework/map"; -import { ReferenceType, reservedTileLabelsKey } from "@fluidframework/merge-tree"; import { IFluidDataStoreContext, IFluidDataStoreFactory, } from "@fluidframework/runtime-definitions"; import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import { SharedString } from "@fluidframework/sequence"; +import { SharedString, ReferenceType, reservedTileLabelsKey } from "@fluidframework/sequence"; import { create404Response } from "@fluidframework/runtime-utils"; import { PresenceManager } from "./presence"; diff --git a/examples/data-objects/codemirror/src/codeMirrorView.tsx b/examples/data-objects/codemirror/src/codeMirrorView.tsx index f1b33cf45142..a0e8889b2711 100644 --- a/examples/data-objects/codemirror/src/codeMirrorView.tsx +++ b/examples/data-objects/codemirror/src/codeMirrorView.tsx @@ -4,13 +4,15 @@ */ import { + getTextAndMarkers, + SharedString, + SequenceDeltaEvent, MergeTreeDeltaType, TextSegment, ReferenceType, reservedTileLabelsKey, Marker, -} from "@fluidframework/merge-tree"; -import { getTextAndMarkers, SharedString, SequenceDeltaEvent } from "@fluidframework/sequence"; +} from "@fluidframework/sequence"; import CodeMirror from "codemirror"; import React, { useEffect, useRef } from "react"; diff --git a/examples/data-objects/diceroller/package.json b/examples/data-objects/diceroller/package.json index c7e185b33c76..6b0a960e44ef 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid Container & Object sample to implement a collaborative dice roller.", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/inventory-app/package.json b/examples/data-objects/inventory-app/package.json index e7242f28952a..12a8eb0dd03e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal sample of SharedTree/React integration.", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/monaco/package.json b/examples/data-objects/monaco/package.json index b7e873c542f7..0f4bbbe1430b 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Monaco code editor", "homepage": "https://fluidframework.com", @@ -42,7 +42,6 @@ "@fluidframework/aqueduct": "workspace:~", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/core-interfaces": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", "@fluidframework/runtime-definitions": "workspace:~", "@fluidframework/sequence": "workspace:~", "monaco-editor": "^0.30.0", diff --git a/examples/data-objects/monaco/src/view.tsx b/examples/data-objects/monaco/src/view.tsx index 3a03976d3b4d..7f38973a670b 100644 --- a/examples/data-objects/monaco/src/view.tsx +++ b/examples/data-objects/monaco/src/view.tsx @@ -4,8 +4,12 @@ */ // inspiration for this example taken from https://github.com/agentcooper/typescript-play -import { MergeTreeDeltaType, TextSegment } from "@fluidframework/merge-tree"; -import { SequenceDeltaEvent, SharedString } from "@fluidframework/sequence"; +import { + SequenceDeltaEvent, + SharedString, + MergeTreeDeltaType, + TextSegment, +} from "@fluidframework/sequence"; import * as monaco from "monaco-editor"; import React, { useEffect, useRef } from "react"; diff --git a/examples/data-objects/multiview/constellation-model/package.json b/examples/data-objects/multiview/constellation-model/package.json index ed8f70d25342..9874eb5074ec 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Constellation model for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/constellation-view/package.json b/examples/data-objects/multiview/constellation-view/package.json index c0512aa989d0..45381b1094d1 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/container/package.json b/examples/data-objects/multiview/container/package.json index 39274c68a08a..12cdb47a0239 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Container for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/coordinate-model/package.json b/examples/data-objects/multiview/coordinate-model/package.json index be5a4a17322d..a91dabc4a0a9 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Coordinate model for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/interface/package.json b/examples/data-objects/multiview/interface/package.json index abe27757506a..4563ee0ede2e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Interface for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/plot-coordinate-view/package.json b/examples/data-objects/multiview/plot-coordinate-view/package.json index 3b5636c5ae9d..cd5e00cb9113 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/slider-coordinate-view/package.json b/examples/data-objects/multiview/slider-coordinate-view/package.json index f39683367dbb..99fa88a491bf 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/triangle-view/package.json b/examples/data-objects/multiview/triangle-view/package.json index 0b6feb399399..390b6fd83975 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "View for multiview sample", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/prosemirror/package.json b/examples/data-objects/prosemirror/package.json index 0c86832a40c8..ae7ce2c22f8c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "ProseMirror", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/prosemirror/src/fluidBridge.ts b/examples/data-objects/prosemirror/src/fluidBridge.ts index 1ec988ec5087..40fa6ae19547 100644 --- a/examples/data-objects/prosemirror/src/fluidBridge.ts +++ b/examples/data-objects/prosemirror/src/fluidBridge.ts @@ -3,25 +3,20 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { assert } from "@fluidframework/core-utils"; +import { createInsertSegmentOp, IMergeTreeDeltaOp } from "@fluidframework/merge-tree"; import { - createInsertSegmentOp, + SharedString, + SequenceDeltaEvent, + ISequenceDeltaRange, Marker, MergeTreeDeltaType, ReferenceType, reservedRangeLabelsKey, TextSegment, ISegment, - IMergeTreeDeltaOp, -} from "@fluidframework/merge-tree"; -import { - SharedString, - // ISequenceDeltaRange, - SequenceDeltaEvent, - ISequenceDeltaRange, } from "@fluidframework/sequence"; import { Schema, @@ -488,7 +483,7 @@ function sliceToGroupOpsInternal( }, }; - const marker = new Marker(ReferenceType.NestBegin); + const marker = new Marker(ReferenceType.Simple); marker.addProperties(beginProps); ops.push(createInsertSegmentOp(from + offset, marker)); @@ -519,7 +514,7 @@ function sliceToGroupOpsInternal( }, }; - const marker = new Marker(ReferenceType.NestEnd); + const marker = new Marker(ReferenceType.Simple); marker.addProperties(endProps); ops.push(createInsertSegmentOp(from + offset, marker)); @@ -534,8 +529,6 @@ function generateFragment(segments: ISegment[]) { const nodeStack = new Array(); nodeStack.push({ type: "doc", content: [] }); - let openTop: IProseMirrorNode | undefined; - // TODO should I pre-seed the data structure based on the nodes to the left of the open? for (const segment of segments) { @@ -559,45 +552,7 @@ function generateFragment(segments: ISegment[]) { top.content!.push(nodeJson); } else if (Marker.is(segment)) { - const nodeType = segment.properties![nodeTypeKey]; switch (segment.refType) { - case ReferenceType.NestBegin: - // Special case the open top - if (openTop) { - top.content!.push(openTop); - openTop = undefined; - } - // Create the new node, add it to the top's content, and push it on the stack - const newNode = { - type: nodeType, - content: [] as IProseMirrorNode[], - _open: true, - }; - top.content!.push(newNode); - nodeStack.push(newNode); - break; - - case ReferenceType.NestEnd: - if (top.type === nodeType) { - top._open = false; - // Matching open - nodeStack.pop(); - } else { - // Unmatched open - const newNode2 = { - type: nodeType, - content: [] as IProseMirrorNode[], - _open: true, - }; - if (openTop) { - newNode2.content.push(openTop); - } - - openTop = newNode2; - } - - break; - case ReferenceType.Simple: // TODO consolidate the text segment and simple references const nodeJson: IProseMirrorNode = { diff --git a/examples/data-objects/prosemirror/src/fluidCollabManager.ts b/examples/data-objects/prosemirror/src/fluidCollabManager.ts index 0276afe0d99a..f2c7b5d658dd 100644 --- a/examples/data-objects/prosemirror/src/fluidCollabManager.ts +++ b/examples/data-objects/prosemirror/src/fluidCollabManager.ts @@ -3,32 +3,19 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { EventEmitter } from "events"; -import { assert } from "@fluidframework/core-utils"; -import { - createGroupOp, - createRemoveRangeOp, - Marker, - ReferenceType, - TextSegment, - IMergeTreeDeltaOp, -} from "@fluidframework/merge-tree"; -import { SharedString } from "@fluidframework/sequence"; +// eslint-disable-next-line import/no-deprecated +import { createGroupOp, createRemoveRangeOp, IMergeTreeDeltaOp } from "@fluidframework/merge-tree"; +import { SharedString, Marker, ReferenceType, TextSegment } from "@fluidframework/sequence"; import { exampleSetup } from "prosemirror-example-setup"; import { DOMSerializer, Schema, Slice } from "prosemirror-model"; import { addListNodes } from "prosemirror-schema-list"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { - IProseMirrorNode, - nodeTypeKey, - ProseMirrorTransactionBuilder, - sliceToGroupOps, -} from "./fluidBridge"; +import { IProseMirrorNode, ProseMirrorTransactionBuilder, sliceToGroupOps } from "./fluidBridge"; import { schema } from "./fluidSchema"; import { create as createSelection } from "./selection"; export const IRichTextEditor: keyof IProvideRichTextEditor = "IRichTextEditor"; @@ -99,20 +86,7 @@ export class FluidCollabManager extends EventEmitter implements IRichTextEditor } else if (Marker.is(segment)) { // TODO are marks applied to the structural nodes as well? Or just inner text? - const nodeType = segment.properties![nodeTypeKey]; switch (segment.refType) { - case ReferenceType.NestBegin: - // Create the new node, add it to the top's content, and push it on the stack - const newNode = { type: nodeType, content: [] }; - top.content!.push(newNode); - nodeStack.push(newNode); - break; - - case ReferenceType.NestEnd: - const popped = nodeStack.pop(); - assert(popped!.type === nodeType, "NestEnd top-node type has wrong type"); - break; - case ReferenceType.Simple: // TODO consolidate the text segment and simple references const nodeJson: IProseMirrorNode = { @@ -282,6 +256,7 @@ export class FluidCollabManager extends EventEmitter implements IRichTextEditor operations = operations.concat(sliceOperations); } + // eslint-disable-next-line import/no-deprecated const groupOp = createGroupOp(...operations); this.text.groupOperation(groupOp); @@ -346,6 +321,7 @@ export class FluidCollabManager extends EventEmitter implements IRichTextEditor operations = operations.concat(sliceOperations); } + // eslint-disable-next-line import/no-deprecated const groupOp = createGroupOp(...operations); this.text.groupOperation(groupOp); diff --git a/examples/data-objects/prosemirror/src/prosemirror.tsx b/examples/data-objects/prosemirror/src/prosemirror.tsx index ff58f91dc91f..b0894ced6d0b 100644 --- a/examples/data-objects/prosemirror/src/prosemirror.tsx +++ b/examples/data-objects/prosemirror/src/prosemirror.tsx @@ -13,20 +13,12 @@ import { mixinRequestHandler, } from "@fluidframework/datastore"; import { ISharedMap, SharedMap } from "@fluidframework/map"; -import { - IMergeTreeInsertMsg, - ReferenceType, - reservedRangeLabelsKey, - MergeTreeDeltaType, - // eslint-disable-next-line import/no-deprecated - createMap, -} from "@fluidframework/merge-tree"; import { IFluidDataStoreContext, IFluidDataStoreFactory, } from "@fluidframework/runtime-definitions"; import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import { SharedString } from "@fluidframework/sequence"; +import { SharedString, ReferenceType, reservedRangeLabelsKey } from "@fluidframework/sequence"; import { EditorView } from "prosemirror-view"; import { create404Response } from "@fluidframework/runtime-utils"; @@ -35,34 +27,23 @@ import React, { useEffect, useRef } from "react"; import { nodeTypeKey } from "./fluidBridge"; import { FluidCollabManager, IProvideRichTextEditor } from "./fluidCollabManager"; -function createTreeMarkerOps( +function insertMarkers( + text: SharedString, treeRangeLabel: string, beginMarkerPos: number, endMarkerPos: number, nodeType: string, -): IMergeTreeInsertMsg[] { - // eslint-disable-next-line import/no-deprecated - const endMarkerProps = createMap(); +) { + const endMarkerProps = {}; endMarkerProps[reservedRangeLabelsKey] = [treeRangeLabel]; endMarkerProps[nodeTypeKey] = nodeType; - // eslint-disable-next-line import/no-deprecated - const beginMarkerProps = createMap(); + const beginMarkerProps = {}; beginMarkerProps[reservedRangeLabelsKey] = [treeRangeLabel]; beginMarkerProps[nodeTypeKey] = nodeType; - return [ - { - seg: { marker: { refType: ReferenceType.NestBegin }, props: beginMarkerProps }, - pos1: beginMarkerPos, - type: MergeTreeDeltaType.INSERT, - }, - { - seg: { marker: { refType: ReferenceType.NestEnd }, props: endMarkerProps }, - pos1: endMarkerPos, - type: MergeTreeDeltaType.INSERT, - }, - ]; + text.insertMarker(endMarkerPos, ReferenceType.Simple, endMarkerProps); + text.insertMarker(beginMarkerPos, ReferenceType.Simple, beginMarkerProps); } /** @@ -113,8 +94,7 @@ export class ProseMirror extends EventEmitter implements IFluidLoadable, IProvid this.root = SharedMap.create(this.runtime, "root"); const text = SharedString.create(this.runtime); - const ops = createTreeMarkerOps("prosemirror", 0, 1, "paragraph"); - text.groupOperation({ ops, type: MergeTreeDeltaType.GROUP }); + insertMarkers(text, "prosemirror", 0, 1, "paragraph"); text.insertText(1, "Hello, world!"); this.root.set("text", text.handle); diff --git a/examples/data-objects/shared-text/.eslintrc.js b/examples/data-objects/shared-text/.eslintrc.js deleted file mode 100644 index 21380e8921d2..000000000000 --- a/examples/data-objects/shared-text/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - extends: [require.resolve("@fluidframework/eslint-config-fluid/minimal"), "prettier"], - rules: { - "@typescript-eslint/prefer-optional-chain": "off", - }, -}; diff --git a/examples/data-objects/shared-text/.gitignore b/examples/data-objects/shared-text/.gitignore deleted file mode 100644 index bde1b1b8a151..000000000000 --- a/examples/data-objects/shared-text/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist -lib -node_modules \ No newline at end of file diff --git a/examples/data-objects/shared-text/.npmignore b/examples/data-objects/shared-text/.npmignore deleted file mode 100644 index a40f882cf599..000000000000 --- a/examples/data-objects/shared-text/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -nyc -*.log -**/*.tsbuildinfo -src/test -dist/test -**/_api-extractor-temp/** diff --git a/examples/data-objects/shared-text/CHANGELOG.md b/examples/data-objects/shared-text/CHANGELOG.md deleted file mode 100644 index 443027e5d510..000000000000 --- a/examples/data-objects/shared-text/CHANGELOG.md +++ /dev/null @@ -1,65 +0,0 @@ -# @fluid-example/shared-text - -## 2.0.0-internal.7.3.0 - -Dependency updates only. - -## 2.0.0-internal.7.2.0 - -Dependency updates only. - -## 2.0.0-internal.7.1.0 - -Dependency updates only. - -## 2.0.0-internal.7.0.0 - -Dependency updates only. - -## 2.0.0-internal.6.4.0 - -Dependency updates only. - -## 2.0.0-internal.6.3.0 - -Dependency updates only. - -## 2.0.0-internal.6.2.0 - -Dependency updates only. - -## 2.0.0-internal.6.1.0 - -Dependency updates only. - -## 2.0.0-internal.6.0.0 - -Dependency updates only. - -## 2.0.0-internal.5.4.0 - -Dependency updates only. - -## 2.0.0-internal.5.3.0 - -Dependency updates only. - -## 2.0.0-internal.5.2.0 - -Dependency updates only. - -## 2.0.0-internal.5.1.0 - -Dependency updates only. - -## 2.0.0-internal.5.0.0 - -Dependency updates only. - -## 2.0.0-internal.4.4.0 - -Dependency updates only. - -## 2.0.0-internal.4.1.0 - -Dependency updates only. diff --git a/examples/data-objects/shared-text/LICENSE b/examples/data-objects/shared-text/LICENSE deleted file mode 100644 index 60af0a6a40e9..000000000000 --- a/examples/data-objects/shared-text/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) Microsoft Corporation and contributors. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/examples/data-objects/shared-text/README.md b/examples/data-objects/shared-text/README.md deleted file mode 100644 index 6d6093df709c..000000000000 --- a/examples/data-objects/shared-text/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# @fluid-example/shared-text - -An experimental implementation of writing a Rich Text Editor. - -## History - -**Shared Text** was the first editor written on the Fluid Framework and has evolved along with the framework. - - - - - - -## Getting Started - -You can run this example using the following steps: - -1. Enable [corepack](https://nodejs.org/docs/latest-v16.x/api/corepack.html) by running `corepack enable`. -1. Run `pnpm install` and `pnpm run build:fast --nolint` from the `FluidFramework` root directory. - - For an even faster build, you can add the package name to the build command, like this: - `pnpm run build:fast --nolint @fluid-example/shared-text` -1. Run `pnpm start` from this directory and open in a web browser to see the app running. - - - - diff --git a/examples/data-objects/shared-text/jest-puppeteer.config.js b/examples/data-objects/shared-text/jest-puppeteer.config.js deleted file mode 100644 index 1ac8aead88a2..000000000000 --- a/examples/data-objects/shared-text/jest-puppeteer.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - server: { - command: `npm run start -- --no-hot --no-live-reload --port ${process.env["PORT"]}`, - port: process.env["PORT"], - launchTimeout: 10000, - usedPortAction: "error", - }, - launch: { - args: ["--no-sandbox", "--disable-setuid-sandbox"], // https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#setting-up-chrome-linux-sandbox - dumpio: process.env.FLUID_TEST_VERBOSE !== undefined, // output browser console to cmd line - // slowMo: 500, // slows down process for easier viewing - // headless: false, // run in the browser - }, -}; diff --git a/examples/data-objects/shared-text/jest.config.js b/examples/data-objects/shared-text/jest.config.js deleted file mode 100644 index 9719717d00e7..000000000000 --- a/examples/data-objects/shared-text/jest.config.js +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -// Get the test port from the global map and set it in env for this test -const testTools = require("@fluidframework/test-tools"); -const { name } = require("./package.json"); - -mappedPort = testTools.getTestPort(name); -process.env["PORT"] = mappedPort; - -module.exports = { - preset: "jest-puppeteer", - globals: { - PATH: `http://localhost:${mappedPort}`, - }, - testMatch: ["**/?(*.)+(spec|test).[t]s"], - testPathIgnorePatterns: ["/node_modules/", "dist"], - transform: { - "^.+\\.ts?$": "ts-jest", - }, - reporters: [ - "default", - [ - "jest-junit", - { - outputDirectory: "nyc", - outputName: "jest-junit-report.xml", - }, - ], - ], -}; diff --git a/examples/data-objects/shared-text/package.json b/examples/data-objects/shared-text/package.json deleted file mode 100644 index 8687b105d385..000000000000 --- a/examples/data-objects/shared-text/package.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "name": "@fluid-example/shared-text", - "version": "2.0.0-internal.7.4.0", - "private": true, - "description": "Shared text", - "homepage": "https://fluidframework.com", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/FluidFramework.git", - "directory": "examples/data-objects/shared-text" - }, - "license": "MIT", - "author": "Microsoft and contributors", - "main": "lib/index.js", - "module": "lib/index.js", - "types": "lib/index.d.ts", - "scripts": { - "build": "fluid-build . --task build", - "build:compile": "fluid-build . --task compile", - "build:esnext": "tsc", - "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 prettier:fix", - "lint": "npm run prettier && npm run eslint", - "lint:fix": "npm run prettier:fix && npm run eslint:fix", - "prepack": "npm run webpack", - "prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore", - "prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore", - "start": "webpack serve --config webpack.config.js", - "start:docker": "webpack serve --config webpack.config.js --env mode=docker", - "start:r11s": "webpack serve --config webpack.config.js --env mode=r11s", - "start:spo": "webpack serve --config webpack.config.js --env mode=spo", - "start:spo-df": "webpack serve --config webpack.config.js --env mode=spo-df", - "start:tinylicious": "webpack serve --config webpack.config.js --env mode=tinylicious", - "test": "npm run test:jest", - "test:jest": "jest", - "test:jest:verbose": "cross-env FLUID_TEST_VERBOSE=1 jest", - "webpack": "webpack --env production", - "webpack:dev": "webpack --env development" - }, - "dependencies": { - "@fluid-example/example-utils": "workspace:~", - "@fluid-internal/client-utils": "workspace:~", - "@fluidframework/aqueduct": "workspace:~", - "@fluidframework/container-definitions": "workspace:~", - "@fluidframework/container-runtime": "workspace:~", - "@fluidframework/core-interfaces": "workspace:~", - "@fluidframework/core-utils": "workspace:~", - "@fluidframework/datastore": "workspace:~", - "@fluidframework/datastore-definitions": "workspace:~", - "@fluidframework/map": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", - "@fluidframework/request-handler": "workspace:~", - "@fluidframework/runtime-definitions": "workspace:~", - "@fluidframework/runtime-utils": "workspace:~", - "@fluidframework/sequence": "workspace:~", - "@fluidframework/undo-redo": "workspace:~", - "@fluidframework/view-interfaces": "workspace:~", - "bootstrap": "^3.3.7", - "debug": "^4.3.4", - "events": "^3.1.0", - "react": "^17.0.1", - "react-dom": "^17.0.1" - }, - "devDependencies": { - "@fluid-tools/webpack-fluid-loader": "workspace:~", - "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.28.0", - "@fluidframework/test-tools": "^1.0.195075", - "@types/expect-puppeteer": "2.2.1", - "@types/jest": "29.5.3", - "@types/jest-environment-puppeteer": "2.2.0", - "@types/loader-utils": "^1", - "@types/node": "^18.19.0", - "@types/prop-types": "^15", - "@types/puppeteer": "1.3.0", - "@types/react": "^17.0.44", - "@types/react-dom": "^17.0.18", - "cross-env": "^7.0.3", - "css-loader": "^1.0.0", - "eslint": "~8.50.0", - "jest": "^29.6.2", - "jest-junit": "^10.0.0", - "jest-puppeteer": "^6.2.0", - "jsdom": "^16.7.0", - "prettier": "~3.0.3", - "process": "^0.11.10", - "puppeteer": "^17.1.3", - "rimraf": "^4.4.0", - "source-map-loader": "^2.0.0", - "style-loader": "^1.0.0", - "ts-loader": "^9.3.0", - "typescript": "~5.1.6", - "url-loader": "^2.1.0", - "webpack": "^5.82.0", - "webpack-cli": "^4.9.2", - "webpack-dev-server": "~4.6.0", - "webpack-merge": "^5.8.0" - }, - "fluid": { - "browser": { - "umd": { - "files": [ - "dist/main.bundle.js" - ], - "library": "main" - } - } - }, - "typeValidation": { - "disabled": true, - "broken": {} - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/README.md b/examples/data-objects/shared-text/src/client-ui-lib/controls/README.md deleted file mode 100644 index 1b1a0d8791c5..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Example Client UI Controls Library - -Provides a set of controls built on top of the example client UI library. - -## Canvas - -## Dock - -## FlowContainer - -## Image - -## Status diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/commandBox.tsx b/examples/data-objects/shared-text/src/client-ui-lib/controls/commandBox.tsx deleted file mode 100644 index 0e5640a99052..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/commandBox.tsx +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import React from "react"; - -export interface ICommandBoxCommand { - friendlyName: string; - exec: () => void; -} - -export interface ICommandBoxProps { - /** - * FlowView wants to summon the command box, easiest way to stitch together is by plumbing a callback. - */ - registerShowListener: (callback: () => void) => void; - /** - * The CommandBox controls its own dismissal. Observers can register a callback to learn when it dismisses. - */ - dismissCallback: () => void; - /** - * The library of commands that the CommandBox will present to the user. - */ - commands: ICommandBoxCommand[]; -} - -export const CommandBox: React.FC = (props: ICommandBoxProps) => { - const { registerShowListener, dismissCallback, commands } = props; - const [show, setShow] = React.useState(false); - const [matchingCommands, setMatchingCommands] = React.useState([]); - const [arrowedCommand, setArrowedCommand] = React.useState(undefined); - - const filterRef = React.useRef(null); - - React.useEffect(() => { - registerShowListener(() => { - setShow(true); - }); - }, [registerShowListener]); - - React.useEffect(() => { - if (filterRef.current !== null) { - const inputEl = filterRef.current; - const filterCommands = () => { - // todo: actually compare match of filtered commands and only reset if different - setArrowedCommand(undefined); - setMatchingCommands( - commands.filter((command) => { - return command.friendlyName - .toLowerCase() - .startsWith(inputEl.value.toLowerCase()); - }), - ); - }; - inputEl.addEventListener("input", filterCommands); - inputEl.focus(); - return () => { - inputEl.removeEventListener("input", filterCommands); - }; - } - }); - - const dismissCommandBox = () => { - setMatchingCommands([]); - setShow(false); - dismissCallback(); - }; - - const keydownHandler = (e: React.KeyboardEvent) => { - switch (e.key) { - case "Escape": { - dismissCommandBox(); - break; - } - case "ArrowDown": { - if (arrowedCommand === undefined) { - if (matchingCommands.length > 0) { - setArrowedCommand(0); - } - } else { - setArrowedCommand((arrowedCommand + 1) % matchingCommands.length); - } - e.preventDefault(); - break; - } - case "ArrowUp": { - if (arrowedCommand === undefined) { - if (matchingCommands.length > 0) { - setArrowedCommand(matchingCommands.length - 1); - } - } else { - setArrowedCommand((arrowedCommand - 1) % matchingCommands.length); - } - e.preventDefault(); - break; - } - default: { - break; - } - } - }; - - const keypressHandler = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - const filterValue = filterRef.current?.value; - const exactMatchingCommand = - filterValue !== undefined - ? commands.find( - (command) => - filterValue.toLowerCase() === command.friendlyName.toLowerCase(), - ) - : undefined; - - if (arrowedCommand !== undefined) { - matchingCommands[arrowedCommand].exec(); - dismissCommandBox(); - } else if (exactMatchingCommand !== undefined) { - exactMatchingCommand.exec(); - dismissCommandBox(); - } - } - }; - - const commandElements = matchingCommands.map((command, index) => { - const clickCommand = () => { - command.exec(); - dismissCommandBox(); - }; - - return index === arrowedCommand ? ( -
- {command.friendlyName} -
- ) : ( -
- {command.friendlyName} -
- ); - }); - - return show ? ( -
-
- -
- {commandElements.length > 0 ? ( -
- {commandElements} -
- ) : ( - <> - )} -
- ) : ( - <> - ); -}; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/cursor.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/cursor.ts deleted file mode 100644 index 755f11f422d0..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/cursor.ts +++ /dev/null @@ -1,134 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export interface IRange { - start: number; - end: number; -} - -const Nope = -1; - -export class Cursor { - public off = true; - private _editSpan: HTMLSpanElement | undefined; - public get editSpan(): HTMLSpanElement { - if (this._editSpan === undefined) { - throw new Error("Edit span accessed before creation"); - } - return this._editSpan; - } - public mark = Nope; - protected bgColor = "blue"; - protected enabled = true; - - constructor( - public viewportDiv: HTMLDivElement, - public pos = 0, - ) { - this.makeSpan(); - } - - /** - * Enable the cursor - makes the cursor visible - */ - public enable() { - if (this.enabled) { - return; - } - - this.enabled = true; - this.show(); - - this.blinkCursor(); - } - - /** - * Disable the cursor - hides the cursor and prevents it from showing up - */ - public disable() { - if (!this.enabled) { - return; - } - - this.enabled = false; - this.hide(true); - this.clearSelection(); - } - - public tryMark() { - if (this.mark === Nope) { - this.mark = this.pos; - } - } - - public emptySelection() { - return this.mark === this.pos; - } - - public clearSelection() { - this.mark = Nope; - } - - public getSelection(): IRange | undefined { - if (this.mark !== Nope) { - return { - end: Math.max(this.mark, this.pos), - start: Math.min(this.mark, this.pos), - }; - } - } - - public hide(hidePresenceDiv: boolean = false) { - this.editSpan.style.visibility = "hidden"; - } - - public show() { - if (!this.enabled) { - return; - } - this.editSpan.style.backgroundColor = this.bgColor; - this.editSpan.style.visibility = "visible"; - } - - public makeSpan() { - this._editSpan = document.createElement("span"); - this.editSpan.innerText = "\uFEFF"; - this.editSpan.style.zIndex = "3"; - this.editSpan.style.position = "absolute"; - this.editSpan.style.left = "0px"; - this.editSpan.style.top = "0px"; - this.editSpan.style.width = "1px"; - this.show(); - } - - public rect() { - return this.editSpan.getBoundingClientRect(); - } - - public scope() { - this.bgColor = "gray"; - this.editSpan.style.backgroundColor = this.bgColor; - this.editSpan.style.zIndex = "4"; - this.editSpan.style.width = "1px"; - } - - public lateralMove(x: number) { - this.editSpan.style.left = `${x}px`; - } - - public assignToLine(x: number, h: number, lineDiv: HTMLDivElement) { - this.editSpan.style.left = `${x}px`; - this.editSpan.style.height = `${h}px`; - if (this.editSpan.parentElement) { - this.editSpan.parentElement.removeChild(this.editSpan); - } - lineDiv.appendChild(this.editSpan); - this.blinkCursor(); - } - - protected blinkCursor() { - this.editSpan.classList.add("blinking"); - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/debug.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/debug.ts deleted file mode 100644 index 497606b046f6..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/debug.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import registerDebug from "debug"; - -export const debug = registerDebug("fluid:controls"); diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/dockPanel.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/dockPanel.ts deleted file mode 100644 index 8e2fc9cbba6f..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/dockPanel.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import * as ui from "../ui"; - -/** - * Basic dock panel control - */ -export class DockPanel extends ui.Component { - public bottom: ui.Component | undefined; - public content: ui.Component | undefined; - public top: ui.Component | undefined; - - constructor(element: HTMLDivElement) { - super(element); - } - - public addContent(content: ui.Component) { - this.content = content; - this.updateChildren(); - } - - public addBottom(bottom: ui.Component) { - this.bottom = bottom; - this.updateChildren(); - } - - public addTop(top: ui.Component) { - this.top = top; - this.updateChildren(); - } - - protected resizeCore(bounds: ui.Rectangle) { - let bottomOffset = 0; - if (this.bottom) { - const result = this.bottom.measure(bounds.size); - bottomOffset = result.height; - } - let topOffset = 0; - if (this.top) { - const result = this.top.measure(bounds.size); - topOffset = result.height; - } - - const split = bounds.nipVertTopBottom(topOffset, bottomOffset); - - this.updateChildBoundsIfExists(this.top, split[0]); - this.updateChildBoundsIfExists(this.content, split[1]); - this.updateChildBoundsIfExists(this.bottom, split[2]); - } - - /** - * Updates the list of children and then forces a resize - */ - private updateChildren() { - this.removeAllChildren(); - ui.removeAllChildren(this.element); - this.addChildIfExists(this.content); - this.addChildIfExists(this.bottom); - this.addChildIfExists(this.top); - this.resizeCore(this.size); - } - - private addChildIfExists(child: ui.Component | undefined) { - if (child) { - this.addChild(child); - this.element.appendChild(child.element); - } - } - - private updateChildBoundsIfExists(child: ui.Component | undefined, bounds: ui.Rectangle) { - if (child) { - bounds.conformElement(child.element); - child.resize(bounds); - } - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/domutils.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/domutils.ts deleted file mode 100644 index 4c858f807602..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/domutils.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -// Called from a few contexts, inclusions are called render -export function clearSubtree(elm: HTMLElement) { - const removeList: Node[] = []; - for (const child of elm.childNodes) { - if (!(child as HTMLElement).classList.contains("preserve")) { - removeList.push(child); - } - } - for (const node of removeList) { - elm.removeChild(node); - } -} - -const textWidthCache = new Map>(); -const lineHeightCache = new Map(); -let cachedCanvas: HTMLCanvasElement; - -export function getLineHeight(fontstr: string, lineHeight?: string) { - let _fontstr = fontstr; - if (lineHeight !== undefined) { - _fontstr += `/${lineHeight}`; - } - let height = lineHeightCache.get(_fontstr); - if (height === undefined) { - const elm = document.createElement("div"); - elm.style.position = "absolute"; - elm.style.zIndex = "-10"; - elm.style.left = "0px"; - elm.style.top = "0px"; - elm.style.font = _fontstr; - document.body.appendChild(elm); - height = getTextHeight(elm); - document.body.removeChild(elm); - lineHeightCache.set(_fontstr, height); - } - if (isNaN(height)) { - console.log(`nan height with fontstr ${_fontstr}`); - } - return height; -} - -export function getTextWidth(text: string, font: string) { - let fontMap = textWidthCache.get(font); - let w: number | undefined; - if (!fontMap) { - fontMap = new Map(); - } else { - w = fontMap.get(text); - } - if (w === undefined) { - const canvas = cachedCanvas ?? (cachedCanvas = document.createElement("canvas")); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const context = canvas.getContext("2d")!; - context.font = font; - const metrics = context.measureText(text); - w = metrics.width; - fontMap.set(text, w); - } - return w; -} - -export function getTextHeight(elm: HTMLDivElement) { - const computedStyle = getComputedStyle(elm); - return computedStyle?.lineHeight.length > 0 && computedStyle.lineHeight !== "normal" - ? parseInt(computedStyle.lineHeight, 10) - : parseInt(computedStyle.fontSize, 10); -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/flowContainer.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/flowContainer.ts deleted file mode 100644 index dabe47c36e23..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/flowContainer.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import * as Sequence from "@fluidframework/sequence"; -import * as ui from "../ui"; -import { DockPanel } from "./dockPanel"; -import { FlowView } from "./flowView"; -import { Title } from "./title"; - -export class FlowContainer extends ui.Component { - public title: Title; - public flowView: FlowView; - private readonly dockPanel: DockPanel; - - // api.Document should not be used. It should be removed after #2915 is fixed. - constructor( - element: HTMLDivElement, - title: string, - private readonly runtime: IFluidDataStoreRuntime, - private readonly sharedString: Sequence.SharedString, - ) { - super(element); - - // TODO the below code is becoming controller like and probably doesn't belong in a constructor. Likely - // a better API model. - - // Title bar at the top - const titleDiv = document.createElement("div"); - titleDiv.id = "title-bar"; - this.title = new Title(titleDiv); - this.title.setTitle(title); - this.title.setBackgroundColor(title); - - // FlowView holds the text - const flowViewDiv = document.createElement("div"); - flowViewDiv.classList.add("flow-view"); - this.flowView = new FlowView(flowViewDiv, this.runtime, this.sharedString); - - this.dockPanel = new DockPanel(this.element); - this.addChild(this.dockPanel); - - // Use the dock panel to layout the viewport - layer panel as the content and then status bar at the bottom - this.dockPanel.addTop(this.title); - this.dockPanel.addContent(this.flowView); - } - - protected resizeCore(bounds: ui.Rectangle) { - bounds.conformElement(this.dockPanel.element); - this.dockPanel.resize(bounds); - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/flowView.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/flowView.ts deleted file mode 100644 index ade6ea4acd2e..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/flowView.ts +++ /dev/null @@ -1,3640 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable import/no-deprecated */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ -/* eslint-disable no-bitwise */ - -import { performance } from "@fluid-internal/client-utils"; -import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import * as types from "@fluidframework/map"; -import * as MergeTree from "@fluidframework/merge-tree"; -import { - debugMarkerToString, - Marker, - refHasRangeLabel, - refHasTileLabel, -} from "@fluidframework/merge-tree"; -import { IClient, ISequencedDocumentMessage, IUser } from "@fluidframework/protocol-definitions"; -import { IInboundSignalMessage } from "@fluidframework/runtime-definitions"; -import * as Sequence from "@fluidframework/sequence"; -import { - SharedSegmentSequenceUndoRedoHandler, - UndoRedoStackManager, -} from "@fluidframework/undo-redo"; -import React from "react"; -import ReactDOM from "react-dom"; -import { CharacterCodes, Paragraph, Table } from "../text"; -import * as ui from "../ui"; -import { CommandBox } from "./commandBox"; -import { Cursor, IRange } from "./cursor"; -import * as domutils from "./domutils"; -import { KeyCode } from "./keycode"; -import { CursorDirection, IViewCursor } from "./layout"; - -interface IFlowViewUser extends IUser { - name: string; -} - -interface ILineDiv extends HTMLDivElement { - linePos?: number; - lineEnd?: number; - contentWidth?: number; - indentWidth?: number; - indentSymbol?: Paragraph.ISymbol; - endPGMarker?: Paragraph.IParagraphMarker; - breakIndex?: number; -} - -interface IRowDiv extends ILineDiv { - rowView: Table.Row; -} - -function findRowParent(lineDiv: ILineDiv) { - let parent = lineDiv.parentElement as IRowDiv; - while (parent) { - if (parent.rowView) { - return parent; - } - parent = parent.parentElement as IRowDiv; - } -} - -interface ISegSpan extends HTMLSpanElement { - seg: MergeTree.TextSegment; - segPos?: number; - offset?: number; - clipOffset?: number; - textErrorRun?: IRange; -} - -interface IRangeInfo { - elm: HTMLElement; - node: Node; - offset: number; -} - -// TODO: mechanism for intelligent services to publish interfaces like this -interface ITextErrorInfo { - text: string; - color?: string; -} - -function elmOffToSegOff(elmOff: IRangeInfo, span: HTMLSpanElement) { - if (elmOff.elm !== span && elmOff.elm.parentElement !== span) { - console.log("did not hit span"); - } - let offset = elmOff.offset; - let prevSib = elmOff.node.previousSibling; - if (!prevSib && elmOff.elm !== span) { - prevSib = elmOff.elm.previousSibling; - } - while (prevSib) { - switch (prevSib.nodeType) { - case Node.ELEMENT_NODE: { - const innerSpan = prevSib as HTMLSpanElement; - offset += innerSpan.innerText.length; - break; - } - case Node.TEXT_NODE: - offset += prevSib.nodeValue!.length; - break; - default: - break; - } - prevSib = prevSib.previousSibling; - } - return offset; -} - -const baseURI = typeof document !== "undefined" ? document.location.origin : ""; -const underlineStringURL = `url("${baseURI}/public/images/underline.gif") bottom repeat-x`; -const underlinePaulStringURL = `url("${baseURI}/public/images/underline-paul.gif") bottom repeat-x`; -const underlinePaulGrammarStringURL = `url("${baseURI}/public/images/underline-paulgrammar.gif") bottom repeat-x`; -const underlinePaulGoldStringURL = `url("${baseURI}/public/images/underline-gold.gif") bottom repeat-x`; - -// global until remove old render -let textErrorRun: IRange | undefined; - -interface ILineContext { - lineDiv: ILineDiv; - contentDiv: HTMLDivElement; - lineDivHeight: number; - flowView: FlowView; - span: ISegSpan; - deferredAttach?: boolean; - reRenderList?: ILineDiv[]; - pgMarker: Paragraph.IParagraphMarker; -} - -export interface IDocumentContext { - wordSpacing: number; - headerFontstr: string; - headerDivHeight: number; - fontstr: string; - defaultLineDivHeight: number; - pgVspace: number; - cellVspace: number; - cellHMargin: number; - cellTopMargin: number; - tableVspace: number; - indentWidthThreshold: number; - viewportDiv: HTMLDivElement; -} - -function buildDocumentContext(viewportDiv: HTMLDivElement) { - const fontstr = "18px Times"; - viewportDiv.style.font = fontstr; - const headerFontstr = "22px Times"; - const wordSpacing = domutils.getTextWidth(" ", fontstr); - const headerDivHeight = 32; - const computedStyle = window.getComputedStyle(viewportDiv); - const defaultLineHeight = 1.2; - const h = parseInt(computedStyle.fontSize, 10); - const defaultLineDivHeight = Math.round(h * defaultLineHeight); - const pgVspace = Math.round(h * 0.5); - const cellVspace = 3; - const tableVspace = pgVspace; - const cellTopMargin = 3; - const cellHMargin = 3; - const indentWidthThreshold = 600; - return { - cellHMargin, - cellTopMargin, - cellVspace, - defaultLineDivHeight, - fontstr, - headerDivHeight, - headerFontstr, - indentWidthThreshold, - pgVspace, - tableVspace, - viewportDiv, - wordSpacing, - } as IDocumentContext; -} - -function showPresence( - presenceX: number, - lineContext: ILineContext, - presenceInfo: ILocalPresenceInfo, -) { - if (!presenceInfo.cursor) { - presenceInfo.cursor = new FlowCursor( - lineContext.flowView.viewportDiv, - presenceInfo.xformPos, - ); - presenceInfo.cursor.addPresenceInfo(presenceInfo); - } - presenceInfo.cursor.assignToLine(presenceX, lineContext.lineDivHeight, lineContext.lineDiv); - presenceInfo.fresh = false; -} - -function showPositionEndOfLine(lineContext: ILineContext, presenceInfo?: ILocalPresenceInfo) { - if (lineContext.deferredAttach) { - addToRerenderList(lineContext); - } else { - if (lineContext.span) { - const cursorBounds = lineContext.span.getBoundingClientRect(); - const lineDivBounds = lineContext.lineDiv.getBoundingClientRect(); - const cursorX = cursorBounds.width + (cursorBounds.left - lineDivBounds.left); - if (!presenceInfo) { - lineContext.flowView.cursor.assignToLine( - cursorX, - lineContext.lineDivHeight, - lineContext.lineDiv, - ); - } else { - showPresence(cursorX, lineContext, presenceInfo); - } - } else { - if (lineContext.lineDiv.indentWidth !== undefined) { - if (!presenceInfo) { - lineContext.flowView.cursor.assignToLine( - lineContext.lineDiv.indentWidth, - lineContext.lineDivHeight, - lineContext.lineDiv, - ); - } else { - showPresence(lineContext.lineDiv.indentWidth, lineContext, presenceInfo); - } - } else { - if (!presenceInfo) { - lineContext.flowView.cursor.assignToLine( - 0, - lineContext.lineDivHeight, - lineContext.lineDiv, - ); - } else { - showPresence(0, lineContext, presenceInfo); - } - } - } - } -} - -function addToRerenderList(lineContext: ILineContext) { - if (!lineContext.reRenderList) { - lineContext.reRenderList = [lineContext.lineDiv]; - } else { - lineContext.reRenderList.push(lineContext.lineDiv); - } -} - -function showPositionInLine( - lineContext: ILineContext, - textStartPos: number, - text: string, - cursorPos: number, - presenceInfo?: ILocalPresenceInfo, -) { - if (lineContext.deferredAttach) { - addToRerenderList(lineContext); - } else { - let posX: number; - const lineDivBounds = lineContext.lineDiv.getBoundingClientRect(); - if (cursorPos > textStartPos) { - const preCursorText = text.substring(0, cursorPos - textStartPos); - const temp = lineContext.span.innerText; - lineContext.span.innerText = preCursorText; - const cursorBounds = lineContext.span.getBoundingClientRect(); - posX = cursorBounds.width + (cursorBounds.left - lineDivBounds.left); - // Console.log(`cbounds w ${cursorBounds.width} posX ${posX} ldb ${lineDivBounds.left}`); - lineContext.span.innerText = temp; - } else { - const cursorBounds = lineContext.span.getBoundingClientRect(); - posX = cursorBounds.left - lineDivBounds.left; - // Console.log(`cbounds whole l ${cursorBounds.left} posX ${posX} ldb ${lineDivBounds.left}`); - } - if (!presenceInfo) { - lineContext.flowView.cursor.assignToLine( - posX, - lineContext.lineDivHeight, - lineContext.lineDiv, - ); - } else { - showPresence(posX, lineContext, presenceInfo); - } - } -} - -function endRenderSegments(marker: MergeTree.Marker) { - return ( - refHasTileLabel(marker, "pg") || - (refHasRangeLabel(marker, "cell") && marker.refType & MergeTree.ReferenceType.NestEnd) - ); -} - -const wordHeadingColor = "rgb(47, 84, 150)"; - -function renderSegmentIntoLine( - segment: MergeTree.ISegment, - segpos: number, - refSeq: number, - clientId: number, - start: number, - end: number, - lineContext: ILineContext, -) { - let _start = start; - let _end = end; - - if (lineContext.lineDiv.linePos === undefined) { - lineContext.lineDiv.linePos = segpos + _start; - lineContext.lineDiv.lineEnd = lineContext.lineDiv.linePos; - } - if (MergeTree.TextSegment.is(segment)) { - if (_start < 0) { - _start = 0; - } - if (_end > segment.cachedLength) { - _end = segment.cachedLength; - } - const text = segment.text.substring(_start, _end); - const textStartPos = segpos + _start; - const textEndPos = segpos + _end; - lineContext.span = makeSegSpan(text, segment, _start, segpos); - if (lineContext.lineDiv.endPGMarker && lineContext.lineDiv.endPGMarker.properties!.header) { - lineContext.span.style.color = wordHeadingColor; - } - lineContext.contentDiv.appendChild(lineContext.span); - lineContext.lineDiv.lineEnd! += text.length; - if ( - lineContext.flowView.cursor.pos >= textStartPos && - lineContext.flowView.cursor.pos <= textEndPos - ) { - showPositionInLine(lineContext, textStartPos, text, lineContext.flowView.cursor.pos); - } - const presenceInfo = lineContext.flowView.presenceInfoInRange(textStartPos, textEndPos); - if (presenceInfo) { - showPositionInLine( - lineContext, - textStartPos, - text, - presenceInfo.xformPos!, - presenceInfo, - ); - } - } else if (MergeTree.Marker.is(segment)) { - // Console.log(`marker pos: ${segpos}`); - - if (endRenderSegments(segment)) { - if (lineContext.flowView.cursor.pos === segpos) { - showPositionEndOfLine(lineContext); - } else { - const presenceInfo = lineContext.flowView.presenceInfoInRange(segpos, segpos); - if (presenceInfo) { - showPositionEndOfLine(lineContext, presenceInfo); - } - } - return false; - } else { - lineContext.lineDiv.lineEnd!++; - } - } - return true; -} - -function findLineDiv(pos: number, flowView: FlowView, dive = false) { - return flowView.lineDivSelect( - (elm) => { - if (elm.linePos! <= pos && elm.lineEnd! > pos) { - return elm; - } - }, - flowView.viewportDiv, - dive, - ); -} - -function decorateLineDiv(lineDiv: ILineDiv, lineFontstr: string, lineDivHeight: number) { - const indentSymbol = lineDiv.indentSymbol!; - let indentFontstr = lineFontstr; - if (indentSymbol.font) { - indentFontstr = indentSymbol.font; - } - const em = Math.round(domutils.getTextWidth("M", lineFontstr)); - const symbolWidth = domutils.getTextWidth(indentSymbol.text, indentFontstr); - const symbolDiv = makeContentDiv( - new ui.Rectangle( - lineDiv.indentWidth! - Math.floor(em + symbolWidth), - 0, - symbolWidth, - lineDivHeight, - ), - indentFontstr, - ); - symbolDiv.innerText = indentSymbol.text; - lineDiv.appendChild(symbolDiv); -} - -function reRenderLine(lineDiv: ILineDiv, flowView: FlowView) { - if (lineDiv) { - const outerViewportBounds = ui.Rectangle.fromClientRect( - flowView.viewportDiv.getBoundingClientRect(), - ); - const lineDivBounds = lineDiv.getBoundingClientRect(); - const lineDivHeight = lineDivBounds.height; - domutils.clearSubtree(lineDiv); - let contentDiv = lineDiv; - if (lineDiv.indentSymbol) { - decorateLineDiv(lineDiv, lineDiv.style.font, lineDivHeight); - } - if (lineDiv.indentWidth) { - contentDiv = makeContentDiv( - new ui.Rectangle(lineDiv.indentWidth, 0, lineDiv.contentWidth!, lineDivHeight), - lineDiv.style.font, - ); - lineDiv.appendChild(contentDiv); - } - const lineContext = { - contentDiv, - flowView, - lineDiv, - lineDivHeight, - markerPos: 0, - outerViewportBounds, - pgMarker: undefined, - span: undefined, - } as unknown as ILineContext; - const lineEnd = lineDiv.lineEnd!; - let end = lineEnd; - if (end === lineDiv.linePos) { - end++; - } - flowView.sharedString.walkSegments( - renderSegmentIntoLine, - lineDiv.linePos, - end, - lineContext, - ); - lineDiv.lineEnd = lineEnd; - } -} - -function makeContentDiv(r: ui.Rectangle, lineFontstr) { - const contentDiv = document.createElement("div"); - contentDiv.style.font = lineFontstr; - contentDiv.style.whiteSpace = "pre"; - contentDiv.onclick = (e) => { - const targetDiv = e.target as HTMLDivElement; - if (targetDiv.lastElementChild) { - console.log( - `div click at ${e.clientX},${e.clientY} rightmost span with text ${targetDiv.lastElementChild.innerHTML}`, - ); - } - }; - r.conformElement(contentDiv); - return contentDiv; -} - -function isInnerCell(cellView: ICellView, layoutInfo: ILayoutContext) { - return ( - !layoutInfo.startingPosStack || - !layoutInfo.startingPosStack.cell || - layoutInfo.startingPosStack.cell.empty() || - layoutInfo.startingPosStack.cell.items.length === layoutInfo.stackIndex! + 1 - ); -} - -interface ICellView extends Table.Cell { - viewport: Viewport; - renderOutput: IRenderOutput; - borderRect: HTMLElement; - svgElm: HTMLElement; -} - -const svgNS = "http://www.w3.org/2000/svg"; - -function createSVGWrapper(w: number, h: number) { - const svg = document.createElementNS(svgNS, "svg") as any as HTMLElement; - svg.style.zIndex = "-1"; - svg.setAttribute("width", w.toString()); - svg.setAttribute("height", h.toString()); - return svg; -} - -function createSVGRect(r: ui.Rectangle) { - const rect = document.createElementNS(svgNS, "rect") as any as HTMLElement; - rect.setAttribute("x", r.x.toString()); - rect.setAttribute("y", r.y.toString()); - rect.setAttribute("width", r.width.toString()); - rect.setAttribute("height", r.height.toString()); - rect.setAttribute("stroke", "darkgrey"); - rect.setAttribute("stroke-width", "1px"); - rect.setAttribute("fill", "none"); - return rect; -} - -function layoutCell(cellView: ICellView, layoutInfo: ILayoutContext) { - const cellRect = new ui.Rectangle(0, 0, cellView.specWidth, 0); - const cellViewportWidth = cellView.specWidth - 2 * layoutInfo.docContext.cellHMargin; - const cellViewportRect = new ui.Rectangle( - layoutInfo.docContext.cellHMargin, - 0, - cellViewportWidth, - 0, - ); - const cellDiv = document.createElement("div"); - cellView.div = cellDiv; - cellRect.conformElementOpenHeight(cellDiv); - const transferDeferredHeight = false; - - cellView.viewport = new Viewport( - layoutInfo.viewport.remainingHeight(), - document.createElement("div"), - cellViewportWidth, - ); - cellViewportRect.conformElementOpenHeight(cellView.viewport.div); - cellDiv.appendChild(cellView.viewport.div); - cellView.viewport.vskip(layoutInfo.docContext.cellTopMargin); - - const cellLayoutInfo = { - deferredAttach: true, - docContext: layoutInfo.docContext, - endMarker: cellView.endMarker, - flowView: layoutInfo.flowView, - requestedPosition: layoutInfo.requestedPosition, - stackIndex: layoutInfo.stackIndex, - startingPosStack: layoutInfo.startingPosStack, - viewport: cellView.viewport, - } as ILayoutContext; - // TODO: deferred height calculation for starting in middle of box - if (isInnerCell(cellView, layoutInfo)) { - const cellPos = getPosition(layoutInfo.flowView.sharedString, cellView.marker); - cellLayoutInfo.startPos = cellPos + cellView.marker.cachedLength; - } else { - const nextTable = layoutInfo.startingPosStack!.table.items[layoutInfo.stackIndex! + 1]; - cellLayoutInfo.startPos = getPosition( - layoutInfo.flowView.sharedString, - nextTable as MergeTree.Marker, - ); - cellLayoutInfo.stackIndex = layoutInfo.stackIndex! + 1; - } - if (!cellView.emptyCell) { - cellView.renderOutput = renderFlow(cellLayoutInfo); - if (cellView.additionalCellMarkers) { - for (const cellMarker of cellView.additionalCellMarkers) { - cellLayoutInfo.endMarker = cellMarker.cell!.endMarker; - const cellPos = getPosition(layoutInfo.flowView.sharedString, cellMarker); - cellLayoutInfo.startPos = cellPos + cellMarker.cachedLength; - const auxRenderOutput = renderFlow(cellLayoutInfo); - cellView.renderOutput.deferredHeight += auxRenderOutput.deferredHeight; - cellView.renderOutput.viewportEndPos = auxRenderOutput.viewportEndPos; - } - } - cellView.viewport.vskip(layoutInfo.docContext.cellVspace); - if (transferDeferredHeight && cellView.renderOutput.deferredHeight > 0) { - layoutInfo.deferUntilHeight = cellView.renderOutput.deferredHeight; - } - } else { - cellView.viewport.vskip(layoutInfo.docContext.defaultLineDivHeight); - cellView.viewport.vskip(layoutInfo.docContext.cellVspace); - cellView.renderOutput = { - deferredHeight: 0, - viewportEndPos: cellLayoutInfo.startPos + 3, - viewportStartPos: cellLayoutInfo.startPos, - }; - } - cellView.renderedHeight = cellLayoutInfo.viewport.getLineTop(); - cellView.svgElm = createSVGWrapper(cellRect.width, cellView.renderedHeight); - cellView.borderRect = createSVGRect( - new ui.Rectangle(0, 0, cellRect.width, cellView.renderedHeight), - ); - cellView.svgElm.appendChild(cellView.borderRect); - cellView.div.appendChild(cellView.svgElm); - if (cellLayoutInfo.reRenderList) { - if (!layoutInfo.reRenderList) { - layoutInfo.reRenderList = []; - } - for (const lineDiv of cellLayoutInfo.reRenderList) { - layoutInfo.reRenderList.push(lineDiv); - } - } -} - -function renderTable( - table: Table.ITableMarker, - docContext: IDocumentContext, - layoutInfo: ILayoutContext, -) { - const flowView = layoutInfo.flowView; - const sharedString = flowView.sharedString; - const tablePos = sharedString.getPosition(table); - let tableView = table.table; - if (!tableView) { - tableView = Table.parseTable( - table, - tablePos, - flowView.sharedString, - makeFontInfo(docContext), - ); - } - if (!tableView) { - return; - } - // Let docContext = buildDocumentContext(viewportDiv); - const viewportWidth = parseInt(layoutInfo.viewport.div.style.width, 10); - - const tableWidth = Math.floor(tableView.contentPct * viewportWidth); - tableView.updateWidth(tableWidth); - const tableIndent = Math.floor(tableView.indentPct * viewportWidth); - let startRow: Table.Row | undefined; - let startCell: ICellView | undefined; - - if (layoutInfo.startingPosStack) { - if ( - layoutInfo.startingPosStack.row && - layoutInfo.startingPosStack.row.items.length > layoutInfo.stackIndex! - ) { - const startRowMarker = layoutInfo.startingPosStack.row.items[ - layoutInfo.stackIndex! - ] as Table.IRowMarker; - startRow = startRowMarker.row; - } - if ( - layoutInfo.startingPosStack.cell && - layoutInfo.startingPosStack.cell.items.length > layoutInfo.stackIndex! - ) { - const startCellMarker = layoutInfo.startingPosStack.cell.items[ - layoutInfo.stackIndex! - ] as Table.ICellMarker; - startCell = startCellMarker.cell as ICellView; - } - } - - let foundStartRow = startRow === undefined; - let tableHeight = 0; - let deferredHeight = 0; - let firstRendered = true; - let topRow = layoutInfo.startingPosStack !== undefined && layoutInfo.stackIndex === 0; - for (let rowIndex = 0, rowCount = tableView.rows.length; rowIndex < rowCount; rowIndex++) { - const rowView = tableView.rows[rowIndex]; - let rowHeight = 0; - if (startRow === rowView) { - foundStartRow = true; - } - const renderRow = - deferredHeight >= layoutInfo.deferUntilHeight! && - foundStartRow && - !Table.rowIsMoribund(rowView.rowMarker); - let rowDiv: IRowDiv | undefined; - if (renderRow) { - const y = layoutInfo.viewport.getLineTop(); - const rowRect = new ui.Rectangle(tableIndent, y, tableWidth, 0); - rowDiv = document.createElement("div") as IRowDiv; - rowDiv.rowView = rowView; - rowRect.conformElementOpenHeight(rowDiv); - if (topRow && startCell) { - layoutCell(startCell, layoutInfo); - deferredHeight += startCell.renderOutput.deferredHeight; - rowHeight = startCell.renderedHeight!; - } - } - let cellX = 0; - for ( - let cellIndex = 0, cellsLen = rowView.cells.length; - cellIndex < cellsLen; - cellIndex++ - ) { - const cell = rowView.cells[cellIndex] as ICellView; - if ((!topRow || cell !== startCell) && !Table.cellIsMoribund(cell.marker)) { - layoutCell(cell, layoutInfo); - if (rowHeight < cell.renderedHeight!) { - rowHeight = cell.renderedHeight!; - } - deferredHeight += cell.renderOutput.deferredHeight; - if (renderRow) { - cell.viewport.div.style.height = `${cell.renderedHeight}px`; - cell.div!.style.height = `${cell.renderedHeight}px`; - cell.div!.style.left = `${cellX}px`; - rowDiv!.appendChild(cell.div!); - } - cellX += cell.specWidth - 1; - } - } - firstRendered = false; - if (renderRow) { - const heightVal = `${rowHeight}px`; - let adjustRowWidth = 0; - for ( - let cellIndex = 0, cellsLen = rowView.cells.length; - cellIndex < cellsLen; - cellIndex++ - ) { - const cell = rowView.cells[cellIndex] as ICellView; - if (cell.div) { - cell.div.style.height = heightVal; - cell.svgElm.setAttribute("height", heightVal); - cell.borderRect.setAttribute("height", heightVal); - } else { - adjustRowWidth += tableView.logicalColumns[cellIndex].width; - } - } - if (rowView.cells.length < tableView.logicalColumns.length) { - for (let col = rowView.cells.length; col < tableView.logicalColumns.length; col++) { - adjustRowWidth += tableView.logicalColumns[col].width; - } - } - let heightAdjust = 0; - if (!firstRendered) { - heightAdjust = 1; - } - tableHeight += rowHeight - heightAdjust; - layoutInfo.viewport.commitLineDiv(rowDiv!, rowHeight - heightAdjust); - rowDiv!.style.height = heightVal; - if (adjustRowWidth) { - rowDiv!.style.width = `${tableWidth - adjustRowWidth}px`; - } - rowDiv!.linePos = rowView.pos; - rowDiv!.lineEnd = rowView.endPos; - layoutInfo.viewport.div.appendChild(rowDiv!); - } - if (topRow) { - topRow = false; - layoutInfo.startingPosStack = undefined; - } - } - if (layoutInfo.reRenderList) { - for (const lineDiv of layoutInfo.reRenderList) { - reRenderLine(lineDiv, flowView); - } - layoutInfo.reRenderList = undefined; - } - tableView.deferredHeight = deferredHeight; - tableView.renderedHeight = tableHeight; -} - -function showCell(pos: number, flowView: FlowView) { - const startingPosStack = flowView.sharedString.getStackContext(pos, ["cell"]); - if (startingPosStack.cell && !startingPosStack.cell.empty()) { - const cellMarker = startingPosStack.cell.top() as Table.ICellMarker; - const start = getPosition(flowView.sharedString, cellMarker); - const endMarker = cellMarker.cell!.endMarker; - const end = getPosition(flowView.sharedString, endMarker) + 1; - setLongStringRepresentationOnMarkers(flowView); - console.log( - `cell ${cellMarker.getId()} seq ${cellMarker.seq} clid ${ - cellMarker.clientId - } at [${start},${end})`, - ); - console.log(`cell contents: ${flowView.sharedString.getTextRangeWithMarkers(start, end)}`); - } -} - -function showTable(pos: number, flowView: FlowView) { - const startingPosStack = flowView.sharedString.getStackContext(pos, ["table"]); - if (startingPosStack.table && !startingPosStack.table.empty()) { - const tableMarker = startingPosStack.table.top() as Table.ITableMarker; - const start = getPosition(flowView.sharedString, tableMarker); - const endMarker = tableMarker.table!.endTableMarker; - const end = getPosition(flowView.sharedString, endMarker) + 1; - setLongStringRepresentationOnMarkers(flowView); - console.log(`table ${tableMarker.getId()} at [${start},${end})`); - console.log(`table contents: ${flowView.sharedString.getTextRangeWithMarkers(start, end)}`); - } -} - -function setLongStringRepresentationOnMarkers(flowView: FlowView) { - flowView.sharedString.walkSegments((segment) => { - if (Marker.is(segment)) { - segment.toString = () => debugMarkerToString(segment); - } - return true; - }); -} - -function renderTree(viewportDiv: HTMLDivElement, requestedPosition: number, flowView: FlowView) { - const docContext = buildDocumentContext(viewportDiv); - flowView.lastDocContext = docContext; - const outerViewportHeight = parseInt(viewportDiv.style.height, 10); - const outerViewportWidth = parseInt(viewportDiv.style.width, 10); - const outerViewport = new Viewport(outerViewportHeight, viewportDiv, outerViewportWidth); - const startingPosStack = flowView.sharedString.getStackContext(requestedPosition, [ - "table", - "cell", - "row", - ]); - const layoutContext = { - docContext, - flowView, - requestedPosition, - viewport: outerViewport, - } as ILayoutContext; - if (startingPosStack.table && !startingPosStack.table.empty()) { - const outerTable = startingPosStack.table.items[0]; - const outerTablePos = flowView.sharedString.getPosition(outerTable as MergeTree.Marker); - layoutContext.startPos = outerTablePos; - layoutContext.stackIndex = 0; - layoutContext.startingPosStack = startingPosStack; - } else { - const previousTileInfo = findTile(flowView.sharedString, requestedPosition, "pg", true); - layoutContext.startPos = previousTileInfo ? previousTileInfo.pos + 1 : 0; - } - return renderFlow(layoutContext); -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface IViewportDiv extends HTMLDivElement {} - -function closestNorth(lineDivs: ILineDiv[], y: number) { - let best = -1; - let lo = 0; - let hi = lineDivs.length - 1; - while (lo <= hi) { - let bestBounds: ClientRect | undefined; - const mid = lo + Math.floor((hi - lo) / 2); - const lineDiv = lineDivs[mid]; - const bounds = lineDiv.getBoundingClientRect(); - if (bounds.bottom <= y) { - if (!bestBounds || best < 0 || bestBounds.bottom < bounds.bottom) { - best = mid; - bestBounds = bounds; - } - lo = mid + 1; - } else { - hi = mid - 1; - } - } - return best; -} - -function closestSouth(lineDivs: ILineDiv[], y: number) { - let best = -1; - let lo = 0; - let hi = lineDivs.length - 1; - while (lo <= hi) { - let bestBounds: ClientRect | undefined; - const mid = lo + Math.floor((hi - lo) / 2); - const lineDiv = lineDivs[mid]; - const bounds = lineDiv.getBoundingClientRect(); - if (bounds.bottom >= y) { - if (!bestBounds || best < 0 || bestBounds.bottom > bounds.bottom) { - best = mid; - bestBounds = bounds; - } - lo = mid + 1; - } else { - hi = mid - 1; - } - } - return best; -} - -interface IExcludedRectangle extends ui.Rectangle { - left: boolean; - curY: number; - id?: string; - // What do the below parameters mean? - requiresUL?: boolean; - floatL?: boolean; -} - -interface ILineRect { - e?: IExcludedRectangle; - h: number; - w: number; - x: number; - y: number; -} - -class Viewport { - // Keep the line divs in order - private readonly lineDivs: ILineDiv[] = []; - private lineTop = 0; - private lineX = 0; - - constructor( - private readonly maxHeight: number, - public div: IViewportDiv, - private readonly width: number, - ) {} - - public firstLineDiv() { - if (this.lineDivs.length > 0) { - return this.lineDivs[0]; - } - } - - public lastLineDiv() { - if (this.lineDivs.length > 0) { - return this.lineDivs[this.lineDivs.length - 1]; - } - } - - public endOfParagraph(h: number) { - if (this.lineX !== 0) { - this.lineX = 0; - this.lineTop += h; - } - } - - public getLineRect(h: number) { - const w = this.width - this.lineX; - this.lineX = 0; - - return { - e: undefined, - h, - w, - x: this.lineX, - y: this.lineTop, - }; - } - - public currentLineWidth(h?: number) { - return this.width; - } - - public vskip(h: number) { - this.lineTop += h; - } - - public getLineX() { - return this.lineX; - } - - public getLineTop() { - return this.lineTop; - } - - public resetTop() { - // TODO: update rect y to 0 and h to h-(deltaY-y) - this.lineTop = 0; - this.lineX = 0; - } - - public setLineTop(v: number) { - this.lineTop = v; - } - - public commitLineDiv(lineDiv: ILineDiv, h: number, eol = true) { - if (eol) { - this.lineTop += h; - } - this.lineDivs.push(lineDiv); - } - - public findClosestLineDiv(up = true, y: number) { - let bestIndex = -1; - bestIndex = up ? closestNorth(this.lineDivs, y) : closestSouth(this.lineDivs, y); - if (bestIndex >= 0) { - return this.lineDivs[bestIndex]; - } - } - - public remainingHeight() { - return this.maxHeight - this.lineTop; - } -} - -interface ILayoutContext { - containingPGMarker?: Paragraph.IParagraphMarker; - viewport: Viewport; - deferredAttach?: boolean; - reRenderList?: ILineDiv[]; - deferUntilHeight?: number; - docContext: IDocumentContext; - requestedPosition?: number; - startPos: number; - endMarker?: MergeTree.Marker; - flowView: FlowView; - stackIndex?: number; - startingPosStack?: MergeTree.RangeStackMap; -} - -interface IRenderOutput { - deferredHeight: number; - // TODO: make this an array for tables that extend past bottom of viewport - viewportStartPos: number; - viewportEndPos: number; -} - -function makeFontInfo(docContext: IDocumentContext): Paragraph.IFontInfo { - const gtw = (text: string, fontstr: string) => domutils.getTextWidth(text, fontstr); - - const glh = (fontstr: string, lineHeight?: string) => - domutils.getLineHeight(fontstr, lineHeight); - - function getFont(pg: Paragraph.IParagraphMarker) { - return pg.properties!.header ? docContext.headerFontstr : docContext.fontstr; - } - - return { - getFont, - getLineHeight: glh, - getTextWidth: gtw, - }; -} - -interface IFlowBreakInfo extends Paragraph.IBreakInfo { - lineY?: number; - lineX?: number; - lineWidth?: number; - lineHeight?: number; - movingExclu?: IExcludedRectangle; -} - -function breakPGIntoLinesFFVP( - flowView: FlowView, - itemInfo: Paragraph.IParagraphItemInfo, - defaultLineHeight: number, - viewport: Viewport, - startOffset = 0, -) { - const items = itemInfo.items; - const savedTop = viewport.getLineTop(); - let lineRect = viewport.getLineRect(itemInfo.maxHeight!); - let breakInfo: IFlowBreakInfo = { - lineHeight: defaultLineHeight, - lineWidth: lineRect.w, - lineX: lineRect.x, - lineY: lineRect.y, - movingExclu: lineRect.e, - posInPG: 0, - startItemIndex: 0, - }; - const breaks = [breakInfo]; - let posInPG = 0; - let committedItemsWidth = 0; - let blockRunWidth = 0; - let blockRunHeight = 0; - let blockRunPos = -1; - let prevIsGlue = true; - let committedItemsHeight = 0; - - function checkViewportFirstLine(pos: number) { - if (pos <= startOffset) { - viewport.resetTop(); - return true; - } - return false; - } - - for (let i = 0, len = items.length; i < len; i++) { - const item = items[i]; - if (item.type === Paragraph.ParagraphItemType.Block) { - item.pos = posInPG; - if (prevIsGlue) { - blockRunPos = posInPG; - blockRunWidth = 0; - } - if (committedItemsWidth + item.width > lineRect.w) { - if (viewport.getLineX() === 0) { - viewport.vskip(committedItemsHeight); - } - checkViewportFirstLine(blockRunPos); - lineRect = viewport.getLineRect(itemInfo.maxHeight!); - breakInfo = { - lineHeight: committedItemsHeight, - lineWidth: lineRect.w, - lineX: lineRect.x, - lineY: lineRect.y, - movingExclu: lineRect.e, - posInPG: blockRunPos, - startItemIndex: i, - }; - breaks.push(breakInfo); - committedItemsWidth = blockRunWidth; - committedItemsHeight = blockRunHeight; - } - posInPG += item.text.length; - if (committedItemsWidth > lineRect.w) { - if (viewport.getLineX() === 0) { - viewport.vskip(committedItemsHeight); - } - checkViewportFirstLine(posInPG); - lineRect = viewport.getLineRect(itemInfo.maxHeight!); - breakInfo = { - lineHeight: committedItemsHeight, - lineWidth: lineRect.w, - lineX: lineRect.x, - lineY: lineRect.y, - movingExclu: lineRect.e, - posInPG, - startItemIndex: i, - }; - breaks.push(breakInfo); - committedItemsWidth = 0; - committedItemsHeight = 0; - blockRunHeight = 0; - blockRunWidth = 0; - blockRunPos = posInPG; - } else { - blockRunWidth += item.width; - blockRunHeight = Math.max( - blockRunHeight, - item.height ? item.height : defaultLineHeight, - ); - } - prevIsGlue = false; - } else if (item.type === Paragraph.ParagraphItemType.Glue) { - posInPG++; - prevIsGlue = true; - } - committedItemsWidth += item.width; - if (item.type !== Paragraph.ParagraphItemType.Marker) { - committedItemsHeight = Math.max( - committedItemsHeight, - item.height ? item.height : defaultLineHeight, - ); - } - } - viewport.endOfParagraph(itemInfo.maxHeight!); - viewport.setLineTop(savedTop); - return breaks; -} - -function renderFlow(layoutContext: ILayoutContext): IRenderOutput { - const flowView = layoutContext.flowView; - const sharedString = flowView.sharedString; - // TODO: for stable viewports cache the geometry and the divs - // TODO: cache all this pre-amble in style blocks; override with pg properties - const docContext = layoutContext.docContext; - let viewportStartPos = -1; - - function makeLineDiv(r: ui.Rectangle, lineFontstr) { - const lineDiv = makeContentDiv(r, lineFontstr); - layoutContext.viewport.div.appendChild(lineDiv); - return lineDiv; - } - - let currentPos = layoutContext.startPos; - let curPGMarker: Paragraph.IParagraphMarker; - let curPGMarkerPos: number; - - // TODO: Should lift into a component-standard layout/render context instead - // of using 'services' to smuggle context to components. - const itemsContext = { - fontInfo: makeFontInfo(layoutContext.docContext), - } as Paragraph.IItemsContext; - if (layoutContext.deferUntilHeight === undefined) { - layoutContext.deferUntilHeight = 0; - } - let deferredHeight = 0; - const paragraphLexer = new Paragraph.ParagraphLexer( - { - markerToken: Paragraph.markerToItems, - textToken: Paragraph.textTokenToItems, - }, - itemsContext, - ); - itemsContext.paragraphLexer = paragraphLexer; - textErrorRun = undefined; - - function renderPG( - endPGMarker: Paragraph.IParagraphMarker, - pgStartPos: number, - indentPct: number, - indentSymbol: Paragraph.ISymbol | undefined, - contentPct: number, - ) { - const pgBreaks = endPGMarker.cache!.breaks; - let lineDiv: ILineDiv | undefined; - let lineDivHeight = docContext.defaultLineDivHeight; - let span: ISegSpan | undefined; - let lineWidth: number | undefined; - let lineX: number | undefined = 0; - let lineY: number | undefined; - let lineFontstr = docContext.fontstr; - lineDivHeight = docContext.defaultLineDivHeight; - if (endPGMarker.properties && endPGMarker.properties.header !== undefined) { - // TODO: header levels etc. - lineDivHeight = docContext.headerDivHeight; - lineFontstr = docContext.headerFontstr; - } - let lineHeight: number | undefined = lineDivHeight; - for (let breakIndex = 0, len = pgBreaks.length; breakIndex < len; breakIndex++) { - const breakInfo = pgBreaks[breakIndex]; - lineY = layoutContext.viewport.getLineTop(); - if (endPGMarker.cache!.isUniformWidth) { - lineWidth = layoutContext.viewport.currentLineWidth(); - } else { - lineWidth = breakInfo.lineWidth!; - lineHeight = breakInfo.lineHeight!; - lineX = breakInfo.lineX!; - lineY = breakInfo.lineY!; - } - let indentWidth = 0; - let contentWidth = lineWidth; - if (indentPct !== 0.0) { - indentWidth = Math.floor(indentPct * lineWidth); - if (docContext.indentWidthThreshold >= lineWidth) { - const em2 = Math.round(2 * domutils.getTextWidth("M", docContext.fontstr)); - indentWidth = em2 + indentWidth; - } - } - contentWidth = Math.floor(contentPct * lineWidth) - indentWidth; - if (contentWidth > lineWidth) { - console.log(`egregious content width ${contentWidth} bound ${lineWidth}`); - } - - const lineStart = breakInfo.posInPG + pgStartPos; - const lineEnd = - breakIndex < len - 1 ? pgBreaks[breakIndex + 1].posInPG + pgStartPos : undefined; - const lineOK = layoutContext.deferUntilHeight! <= deferredHeight; - if (lineOK && (lineEnd === undefined || lineEnd > layoutContext.requestedPosition!)) { - lineDiv = makeLineDiv( - new ui.Rectangle(lineX, lineY, lineWidth, lineHeight), - lineFontstr, - ); - lineDiv.endPGMarker = endPGMarker; - lineDiv.breakIndex = breakIndex; - let contentDiv = lineDiv; - if (indentWidth > 0) { - contentDiv = makeContentDiv( - new ui.Rectangle(indentWidth, 0, contentWidth, lineDivHeight), - lineFontstr, - ); - lineDiv.indentWidth = indentWidth; - lineDiv.contentWidth = indentWidth; - if (indentSymbol && breakIndex === 0) { - lineDiv.indentSymbol = indentSymbol; - decorateLineDiv(lineDiv, lineFontstr, lineDivHeight); - } - lineDiv.appendChild(contentDiv); - } - const lineContext = { - contentDiv, - deferredAttach: layoutContext.deferredAttach, - flowView: layoutContext.flowView, - lineDiv, - lineDivHeight, - pgMarker: endPGMarker, - span, - } as ILineContext; - if (viewportStartPos < 0) { - viewportStartPos = lineStart; - } - sharedString.walkSegments(renderSegmentIntoLine, lineStart, lineEnd, lineContext); - span = lineContext.span; - if (lineContext.reRenderList) { - if (!layoutContext.reRenderList) { - layoutContext.reRenderList = []; - } - for (const ldiv of lineContext.reRenderList) { - layoutContext.reRenderList.push(ldiv); - } - } - let eol = lineX + lineWidth >= layoutContext.viewport.currentLineWidth(); - eol = eol || lineEnd === undefined; - layoutContext.viewport.commitLineDiv(lineDiv, lineDivHeight, eol); - } else { - deferredHeight += lineDivHeight; - } - - if (layoutContext.viewport.remainingHeight() < docContext.defaultLineDivHeight) { - // No more room for lines - break; - } - } - return lineDiv!.lineEnd!; - } - - const fetchLog = false; - let segoff: ISegmentOffset | undefined; - const totalLength = sharedString.getLength(); - let viewportEndPos: number = currentPos; - // TODO: use end of doc marker - do { - if (!segoff) { - segoff = getContainingSegment(flowView.sharedString, currentPos); - } - if (fetchLog) { - console.log(`got segment ${segoff.segment?.toString()}`); - } - if (!segoff.segment) { - break; - } - - const asMarker = MergeTree.Marker.is(segoff.segment) ? segoff.segment : undefined; - - if (asMarker && refHasRangeLabel(asMarker, "table")) { - let tableView: Table.Table; - if (asMarker.removedSeq === undefined) { - renderTable(asMarker, docContext, layoutContext); - tableView = (asMarker as Table.ITableMarker).table!; - deferredHeight += tableView.deferredHeight!; - layoutContext.viewport.vskip(layoutContext.docContext.tableVspace); - } else { - tableView = Table.parseTable( - asMarker, - currentPos, - flowView.sharedString, - makeFontInfo(layoutContext.docContext), - )!; - } - const endTablePos = getPosition( - layoutContext.flowView.sharedString, - tableView.endTableMarker, - ); - currentPos = endTablePos + 1; - segoff = undefined; - // TODO: if reached end of viewport, get pos ranges - } else { - if (asMarker) { - // Empty paragraph - curPGMarker = segoff.segment as Paragraph.IParagraphMarker; - if (fetchLog) { - console.log("empty pg"); - if (curPGMarker.itemCache) { - console.log(`length items ${curPGMarker.itemCache.items.length}`); - } - } - curPGMarkerPos = currentPos; - } else { - const curTilePos = findTile(flowView.sharedString, currentPos, "pg", false); - curPGMarker = curTilePos?.tile as Paragraph.IParagraphMarker; - curPGMarkerPos = curTilePos?.pos ?? 0; - } - itemsContext.curPGMarker = curPGMarker; - // TODO: only set this to undefined if text changed - curPGMarker.listCache = undefined; - Paragraph.getListCacheInfo( - layoutContext.flowView.sharedString, - curPGMarker, - curPGMarkerPos, - ); - let indentSymbol: Paragraph.ISymbol | undefined; - const indentPct = Paragraph.getIndentPct(curPGMarker); - const contentPct = Paragraph.getContentPct(curPGMarker); - - if (curPGMarker.listCache) { - indentSymbol = Paragraph.getIndentSymbol(curPGMarker); - } - if (!curPGMarker.itemCache) { - itemsContext.itemInfo = { items: [], minWidth: 0 }; - sharedString.walkSegments( - Paragraph.segmentToItems, - currentPos, - curPGMarkerPos + 1, - itemsContext, - ); - curPGMarker.itemCache = itemsContext.itemInfo; - } else { - itemsContext.itemInfo = curPGMarker.itemCache; - } - let startOffset = 0; - if (layoutContext.requestedPosition! > currentPos) { - startOffset = layoutContext.requestedPosition! - currentPos; - } - const breaks = breakPGIntoLinesFFVP( - layoutContext.flowView, - itemsContext.itemInfo, - docContext.defaultLineDivHeight, - layoutContext.viewport, - startOffset, - ); - curPGMarker.cache = { breaks, isUniformWidth: false }; - paragraphLexer.reset(); - // TODO: more accurate end of document reasoning - - if (currentPos < totalLength) { - const lineEnd = renderPG( - curPGMarker, - currentPos, - indentPct, - indentSymbol, - contentPct, - ); - viewportEndPos = lineEnd; - currentPos = curPGMarkerPos + curPGMarker.cachedLength; - - if (currentPos < totalLength) { - segoff = getContainingSegment(flowView.sharedString, currentPos); - if (segoff.segment && MergeTree.Marker.is(segoff.segment)) { - if ( - refHasRangeLabel(segoff.segment, "cell") && - segoff.segment.refType & MergeTree.ReferenceType.NestEnd - ) { - break; - } - } - } else { - break; - } - layoutContext.viewport.vskip(docContext.pgVspace); - } else { - break; - } - } - } while (layoutContext.viewport.remainingHeight() >= docContext.defaultLineDivHeight); - - return { - deferredHeight, - viewportEndPos, - viewportStartPos, - }; -} - -function makeSegSpan( - segText: string, - textSegment: MergeTree.TextSegment, - offsetFromSegpos: number, - segpos: number, -) { - const span = document.createElement("span") as ISegSpan; - span.innerText = segText; - span.seg = textSegment; - span.segPos = segpos; - let textErr = false; - if (textSegment.properties) { - // eslint-disable-next-line no-restricted-syntax - for (const key in textSegment.properties) { - if (key === "textError") { - textErr = true; - if (textErrorRun === undefined) { - textErrorRun = { - end: segpos + offsetFromSegpos + segText.length, - start: segpos + offsetFromSegpos, - }; - } else { - textErrorRun.end += segText.length; - } - const textErrorInfo = textSegment.properties[key] as ITextErrorInfo; - span.textErrorRun = textErrorRun; - - switch (textErrorInfo.color) { - case "paul": { - span.style.background = underlinePaulStringURL; - break; - } - case "paulgreen": { - span.style.background = underlinePaulGrammarStringURL; - break; - } - case "paulgolden": { - span.style.background = underlinePaulGoldStringURL; - break; - } - default: { - span.style.background = underlineStringURL; - break; - } - } - } else { - span.style[key] = textSegment.properties[key]; - } - } - } - if (!textErr) { - textErrorRun = undefined; - } - if (offsetFromSegpos > 0) { - span.offset = offsetFromSegpos; - } - return span; -} - -function pointerToElementOffsetWebkit(x: number, y: number): IRangeInfo | undefined { - const range = document.caretRangeFromPoint(x, y); - if (range) { - const result = { - elm: range.startContainer.parentElement!, - node: range.startContainer, - offset: range.startOffset, - }; - range.detach(); - return result; - } -} - -const Nope = -1; - -const presenceColors = ["darkgreen", "sienna", "olive", "purple", "lightseagreen"]; - -class FlowCursor extends Cursor { - private presenceDiv: HTMLDivElement | undefined; - public presenceInfo: ILocalPresenceInfo | undefined; - public presenceInfoUpdated = true; - - constructor( - public viewportDiv: HTMLDivElement, - public pos = 0, - ) { - super(viewportDiv, pos); - } - - public hide(hidePresenceDiv: boolean = false) { - this.editSpan.style.visibility = "hidden"; - - if (hidePresenceDiv && this.presenceInfo) { - this.presenceDiv!.style.visibility = "hidden"; - } - } - - public show() { - if (!this.enabled) { - return; - } - - this.editSpan.style.backgroundColor = this.bgColor; - this.editSpan.style.visibility = "visible"; - - if (this.presenceInfo) { - this.presenceDiv!.style.visibility = "visible"; - } - } - - /** - * Refreshes the cursor - * It will enable / disable the cursor depending on if the client is connected - */ - public refresh() { - if (this.presenceInfo) { - if (this.presenceInfo.shouldShowCursor()) { - this.enable(); - } else { - this.disable(); - } - } - } - public addPresenceInfo(presenceInfo: ILocalPresenceInfo) { - // For now, color - this.bgColor = presenceInfo.presenceColor; - this.presenceInfo = presenceInfo; - this.makePresenceDiv(); - - this.refresh(); - - if (this.enabled) { - this.show(); - } else { - this.hide(true); - } - } - - private setPresenceDivEvents(div: HTMLDivElement) { - this.presenceDiv!.onmouseenter = (e) => { - div.innerText = (this.presenceInfo!.user as IFlowViewUser).name; - }; - this.presenceDiv!.onmouseleave = (e) => { - div.innerText = this.getUserDisplayString(this.presenceInfo!.user as IFlowViewUser); - }; - } - - private makePresenceDiv() { - this.presenceDiv = document.createElement("div"); - // TODO callback to go from UID to display information - this.presenceDiv.innerText = this.getUserDisplayString( - this.presenceInfo!.user as IFlowViewUser, - ); - this.presenceDiv.style.zIndex = "1"; - this.presenceDiv.style.position = "absolute"; - this.presenceDiv.style.color = "white"; - this.presenceDiv.style.backgroundColor = this.bgColor; - this.presenceDiv.style.font = "10px Arial"; - this.presenceDiv.style.border = `2px solid ${this.bgColor}`; - this.presenceDiv.style.borderTopRightRadius = "1em"; - this.setPresenceDivEvents(this.presenceDiv); - // Go underneath local cursor - this.editSpan.style.zIndex = "1"; - } - - public onLine(pos: number) { - const lineDiv = this.lineDiv(); - return lineDiv && pos >= lineDiv.linePos! && pos < lineDiv.lineEnd!; - } - - public lineDiv() { - return this.editSpan.parentElement as ILineDiv; - } - - public updateView(flowView: FlowView) { - if (this.getSelection()) { - flowView.render(flowView.topChar, true); - } else { - const lineDiv = this.lineDiv(); - if (lineDiv && lineDiv.linePos! <= this.pos && lineDiv.lineEnd! > this.pos) { - reRenderLine(lineDiv, flowView); - } else { - const foundLineDiv = findLineDiv(this.pos, flowView, true); - if (foundLineDiv) { - reRenderLine(foundLineDiv, flowView); - } else { - flowView.render(flowView.topChar, true); - } - } - } - } - - public assignToLine(x: number, h: number, lineDiv: HTMLDivElement, show = true) { - this.editSpan.style.left = `${x}px`; - this.editSpan.style.height = `${h}px`; - if (this.editSpan.parentElement) { - this.editSpan.parentElement.removeChild(this.editSpan); - } - lineDiv.appendChild(this.editSpan); - if (this.presenceInfo) { - const bannerHeight = 16; - const halfBannerHeight = bannerHeight / 2; - this.presenceDiv!.style.left = `${x}px`; - this.presenceDiv!.style.height = `${bannerHeight}px`; - this.presenceDiv!.style.top = `-${halfBannerHeight}px`; - if (this.presenceDiv!.parentElement) { - this.presenceDiv!.parentElement.removeChild(this.presenceDiv!); - } - lineDiv.appendChild(this.presenceDiv!); - this.setPresenceDivEvents(this.presenceDiv!); - } - if (!this.presenceInfo || this.presenceInfo.fresh) { - if (this.presenceInfo) { - this.editSpan.style.opacity = "0.6"; - this.presenceDiv!.style.opacity = "0.6"; - } - if (show) { - this.show(); - this.blinkCursor(); - } else { - this.hide(); - } - } - } - - protected blinkCursor() { - if (this.presenceDiv) { - // This.editSpan.classList.add("brieflyBlinking"); - // this.presenceDiv.classList.add("brieflyBlinking"); - } else { - super.blinkCursor(); - } - } - - private getUserDisplayString(user: IFlowViewUser): string { - // TODO - callback to client code to provide mapping from user -> display - // this would allow a user ID to be put on the wire which can then be mapped - // back to an email, name, etc... - const name = user.name; - const nameParts = name.split(" "); - let initials = ""; - for (const part of nameParts) { - initials += part.substring(0, 1); - } - return initials; - } -} - -interface IRemotePresenceBase { - type: string; -} -interface ILocalPresenceInfo { - localRef?: MergeTree.ReferencePosition; - markLocalRef?: MergeTree.ReferencePosition; - xformPos?: number; - markXformPos?: number; - clientId: string; - presenceColor: string; - user: IUser; - cursor?: FlowCursor; - fresh: boolean; - shouldShowCursor: () => boolean; -} - -interface IRemotePresenceInfo extends IRemotePresenceBase { - type: "selection"; - origPos: number; - origMark: number; - refseq: number; -} - -interface ISegmentOffset { - segment: MergeTree.ISegment | undefined; - offset: number | undefined; -} - -interface IWordRange { - wordStart: number; - wordEnd: number; -} - -function getCurrentWord(pos: number, sharedString: Sequence.SharedString) { - let wordStart = -1; - let wordEnd = -1; - - function maximalWord(textSegment: MergeTree.TextSegment, offset: number) { - let segWordStart = offset; - let segWordEnd = offset; - - let epos = offset; - const nonWord = /\W/; - while (epos < textSegment.text.length) { - if (nonWord.test(textSegment.text.charAt(epos))) { - break; - } - epos++; - } - segWordEnd = epos; - if (segWordEnd > offset) { - let spos = offset - 1; - while (spos >= 0) { - if (nonWord.test(textSegment.text.charAt(spos))) { - break; - } - spos--; - } - segWordStart = spos + 1; - } - return { wordStart: segWordStart, wordEnd: segWordEnd } as IWordRange; - } - - const segoff = sharedString.getContainingSegment(pos); - if (segoff.segment && MergeTree.TextSegment.is(segoff.segment)) { - const maxWord = maximalWord(segoff.segment, segoff.offset ?? 0); - if (maxWord.wordStart < maxWord.wordEnd) { - const segStartPos = pos - (segoff.offset ?? 0); - wordStart = segStartPos + maxWord.wordStart; - wordEnd = segStartPos + maxWord.wordEnd; - if (maxWord.wordStart === 0) { - // Expand word backward - let leftPos = segStartPos; - while (leftPos > 0 && leftPos === wordStart) { - const leftSeg = sharedString.getContainingSegment(leftPos - 1).segment!; - if (MergeTree.TextSegment.is(leftSeg)) { - const mword = maximalWord(leftSeg, leftSeg.cachedLength - 1); - wordStart -= mword.wordEnd - mword.wordStart; - } - leftPos -= leftSeg.cachedLength; - } - } - if (maxWord.wordEnd === segoff.segment.text.length) { - // Expand word forward - let rightPos = segStartPos + segoff.segment.cachedLength; - while (rightPos < sharedString.getLength() && rightPos === wordEnd) { - const rightSeg = sharedString.getContainingSegment(rightPos).segment!; - if (MergeTree.TextSegment.is(rightSeg)) { - const mword = maximalWord(rightSeg, 0); - wordEnd += mword.wordEnd; - } - rightPos += rightSeg.cachedLength; - } - } - } - if (wordStart >= 0) { - return { wordStart, wordEnd } as IWordRange; - } - } -} - -function getLocalRefPos( - sharedString: Sequence.SharedString, - localRef: MergeTree.ReferencePosition, -) { - return sharedString.localReferencePositionToPosition(localRef); -} - -function getContainingSegment(sharedString: Sequence.SharedString, pos: number): ISegmentOffset { - return sharedString.getContainingSegment(pos); -} - -function findTile( - sharedString: Sequence.SharedString, - startPos: number, - tileType: string, - preceding: boolean, -) { - return sharedString.findTile(startPos, tileType, preceding); -} - -function getPosition(sharedString: Sequence.SharedString, segment: MergeTree.ISegment) { - return sharedString.getPosition(segment); -} - -function preventD(e: Event) { - e.returnValue = false; - e.preventDefault(); - return false; -} - -const presenceSignalType = "presence"; - -export class FlowView extends ui.Component { - public static docStartPosition = 0; - public timeToImpression: number | undefined; - public timeToEdit: number | undefined; - public viewportStartPos: number | undefined; - public viewportEndPos: number | undefined; - public childCursor: IViewCursor | undefined; - public viewportDiv: HTMLDivElement; - public viewportRect: ui.Rectangle | undefined; - public ticking = false; - public wheelTicking = false; - public topChar = -1; - public cursor: FlowCursor; - public presenceVector: Map = new Map(); - public curPG: MergeTree.Marker | undefined; - public lastDocContext: IDocumentContext | undefined; - public focusChild: FlowView | undefined; - public parentFlow: FlowView | undefined; - public keypressHandler: ((e: KeyboardEvent) => void) | undefined; - public keydownHandler: ((e: KeyboardEvent) => void) | undefined; - - public srcLanguage = "en"; - - private lastVerticalX = -1; - private pendingRender = false; - private activeCommandBox: boolean | undefined; - private formatRegister: MergeTree.PropertySet | undefined; - - // A list of Marker segments modified by the most recently processed op. (Reset on each - // sequenceDelta event.) Used by 'updatePgInfo()' to determine if table information - // may have been invalidated. - private modifiedMarkers: any[] = []; - - private readonly undoRedoManager: UndoRedoStackManager; - - private showCommandBox: () => void = () => {}; - - constructor( - element: HTMLDivElement, - public readonly runtime: IFluidDataStoreRuntime, - public sharedString: Sequence.SharedString, - ) { - super(element); - - // Enable element to receive focus (see Example 1): - // https://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/SCR29.html - this.element.tabIndex = 0; - - // Disable visible focus outline when FlowView is focused. - this.element.style.outline = "0px solid transparent"; - - // Clip children of FlowView to the bounds of the FlowView's root div. - this.element.style.overflow = "hidden"; - - this.viewportDiv = document.createElement("div"); - this.element.appendChild(this.viewportDiv); - - this.undoRedoManager = new UndoRedoStackManager(); - const sequenceHandler = new SharedSegmentSequenceUndoRedoHandler(this.undoRedoManager); - sequenceHandler.attachSequence(sharedString); - - sharedString.on("sequenceDelta", (event, target) => { - // For each incoming delta, save any referenced Marker segments. - // (see comments at 'modifiedMarkers' decl for more info.) - this.modifiedMarkers = event.ranges.filter((range) => - MergeTree.Marker.is(range.segment), - ); - - this.handleSharedStringDelta(event, target); - }); - - // Refresh cursors when clients join or leave - runtime.getQuorum().on("addMember", () => { - this.updatePresenceCursors(); - this.broadcastPresence(); - }); - runtime.getQuorum().on("removeMember", () => { - this.updatePresenceCursors(); - }); - runtime.getAudience().on("addMember", () => { - this.updatePresenceCursors(); - this.broadcastPresence(); - }); - runtime.getAudience().on("removeMember", () => { - this.updatePresenceCursors(); - }); - - this.cursor = new FlowCursor(this.viewportDiv); - - // Not great construction -- this slack wrapper div lets the command box use the flow-view div above act as - // its containing box while remaining out of the way for hit testing, etc. Once FlowView is also React, it - // should be easier to coordinate the layout. - const commandBoxDiv = document.createElement("div"); - commandBoxDiv.classList.add("command-box-wrapper"); - this.element.appendChild(commandBoxDiv); - - const registerShowListener = (callback: () => void) => { - this.showCommandBox = callback; - }; - const onCommandBoxDismiss = () => { - this.activeCommandBox = false; - }; - const commandBoxCommands = [ - { - friendlyName: "copy format", - exec: () => { - this.copyFormat(); - }, - }, - { - friendlyName: "paint format", - exec: () => { - this.paintFormat(); - }, - }, - { - friendlyName: "blockquote", - exec: () => { - this.toggleBlockquote(); - }, - }, - { - friendlyName: "bold", - exec: () => { - this.toggleBold(); - }, - }, - { - friendlyName: "red", - exec: () => { - this.setColor("red"); - }, - }, - { - friendlyName: "green", - exec: () => { - this.setColor("green"); - }, - }, - { - friendlyName: "gold", - exec: () => { - this.setColor("gold"); - }, - }, - { - friendlyName: "pink", - exec: () => { - this.setColor("pink"); - }, - }, - { - friendlyName: "Courier font", - exec: () => { - this.setFont("courier new", "18px"); - }, - }, - { - friendlyName: "Tahoma", - exec: () => { - this.setFont("tahoma", "18px"); - }, - }, - { - friendlyName: "Heading 2", - exec: () => { - this.setPGProps({ header: true }); - }, - }, - { - friendlyName: "Normal", - exec: () => { - this.setPGProps({ header: undefined }); - }, - }, - { - friendlyName: "Georgia font", - exec: () => { - this.setFont("georgia", "18px"); - }, - }, - { - friendlyName: "sans font", - exec: () => { - this.setFont("sans-serif", "18px"); - }, - }, - { - friendlyName: "cursive font", - exec: () => { - this.setFont("cursive", "18px"); - }, - }, - { - friendlyName: "italic", - exec: () => { - this.toggleItalic(); - }, - }, - { - friendlyName: "list ... 1.)", - exec: () => { - this.setList(); - }, - }, - { - friendlyName: "list ... \u2022", - exec: () => { - this.setList(1); - }, - }, - { - friendlyName: "cell info", - exec: () => { - showCell(this.cursor.pos, this); - }, - }, - { - friendlyName: "table info", - exec: () => { - showTable(this.cursor.pos, this); - }, - }, - { - friendlyName: "table summary", - exec: () => { - this.tableSummary(); - }, - }, - { - friendlyName: "table test", - exec: () => { - this.updatePGInfo(this.cursor.pos - 1); - Table.createTable(this.cursor.pos, this.sharedString, this.runtime.clientId!); - this.hostSearchMenu(this.cursor.pos); - }, - }, - { - friendlyName: "insert column", - exec: () => { - this.insertColumn(); - }, - }, - { - friendlyName: "insert row", - exec: () => { - this.insertRow(); - }, - }, - { - friendlyName: "delete row", - exec: () => { - this.deleteRow(); - }, - }, - { - friendlyName: "delete column", - exec: () => { - this.deleteColumn(); - }, - }, - { - friendlyName: "underline", - exec: () => { - this.toggleUnderline(); - }, - }, - ]; - - const commandBoxElement = React.createElement(CommandBox, { - registerShowListener, - dismissCallback: onCommandBoxDismiss, - commands: commandBoxCommands, - }); - - ReactDOM.render(commandBoxElement, commandBoxDiv); - } - - private updatePresenceCursors() { - for (const presenceInfo of this.presenceVector.values()) { - if (presenceInfo && presenceInfo.cursor) { - presenceInfo.cursor.refresh(); - } - } - } - - public presenceInfoInRange(start: number, end: number) { - for (const presenceInfo of this.presenceVector.values()) { - if (presenceInfo) { - if (start <= presenceInfo.xformPos! && presenceInfo.xformPos! <= end) { - return presenceInfo; - } - } - } - } - - private updatePresencePosition(localPresenceInfo: ILocalPresenceInfo) { - if (localPresenceInfo) { - localPresenceInfo.xformPos = getLocalRefPos( - this.sharedString, - localPresenceInfo.localRef!, - ); - localPresenceInfo.markXformPos = localPresenceInfo.markLocalRef - ? getLocalRefPos(this.sharedString, localPresenceInfo.markLocalRef) - : localPresenceInfo.xformPos; - } - } - - private updatePresencePositions() { - for (const presenceInfo of this.presenceVector.values()) { - this.updatePresencePosition(presenceInfo); - } - } - - private updatePresenceVector(localPresenceInfo: ILocalPresenceInfo) { - this.updatePresencePosition(localPresenceInfo); - const presentPresence = this.presenceVector[localPresenceInfo.clientId]; - let tempXformPos = -1; - let tempMarkXformPos = -2; - - if (presentPresence) { - if (presentPresence.cursor) { - localPresenceInfo.cursor = presentPresence.cursor; - localPresenceInfo.cursor!.presenceInfo = localPresenceInfo; - localPresenceInfo.cursor!.presenceInfoUpdated = true; - } - if (presentPresence.markLocalRef) { - this.sharedString.removeLocalReferencePosition(presentPresence.markLocalRef); - } - this.sharedString.removeLocalReferencePosition(presentPresence.localRef); - tempXformPos = presentPresence.xformPos; - tempMarkXformPos = presentPresence.markXformPos; - } - this.presenceVector.set(localPresenceInfo.clientId, localPresenceInfo); - if ( - localPresenceInfo.xformPos !== tempXformPos || - localPresenceInfo.markXformPos !== tempMarkXformPos - ) { - const sameLine = - localPresenceInfo.cursor && - localPresenceInfo.cursor.onLine(tempXformPos) && - localPresenceInfo.cursor.onLine(tempMarkXformPos) && - localPresenceInfo.cursor.onLine(localPresenceInfo.xformPos!) && - localPresenceInfo.cursor.onLine(localPresenceInfo.markXformPos!); - this.presenceQueueRender(localPresenceInfo, sameLine); - } - } - - public firstLineDiv() { - return this.lineDivSelect((elm) => elm, this.viewportDiv, false); - } - - public lastLineDiv() { - return this.lineDivSelect((elm) => elm, this.viewportDiv, false, true); - } - - /** - * Returns the (x, y) coordinate of the given position relative to the FlowView's coordinate system or `undefined` - * if the position is not visible. - */ - private getPositionLocation(position: number): ui.IPoint | undefined { - const lineDiv = findLineDiv(position, this, true); - if (!lineDiv) { - return undefined; - } - - // Estimate placement location - const text = this.sharedString.getText(lineDiv.linePos, position); - const textWidth = domutils.getTextWidth(text, lineDiv.style.font); - const lineDivRect = lineDiv.getBoundingClientRect(); - - const location = { x: lineDivRect.left + textWidth, y: lineDivRect.bottom }; - - return location; - } - - /** - * Retrieves the nearest sequence position relative to the given viewport location - */ - public getNearestPosition(location: ui.IPoint): number | undefined { - const lineDivs: ILineDiv[] = []; - this.lineDivSelect( - (lineDiv) => { - lineDivs.push(lineDiv); - return undefined; - }, - this.viewportDiv, - false, - ); - - // Search for the nearest line divs to the element - const closestUp = closestNorth(lineDivs, location.y); - const closestDown = closestSouth(lineDivs, location.y); - - // And then the nearest location within them - let distance = Number.MAX_VALUE; - let position: number | undefined; - - if (closestUp !== -1) { - const upPosition = this.getPosFromPixels(lineDivs[closestUp], location.x)!; - const upLocation = this.getPositionLocation(upPosition)!; - distance = ui.distanceSquared(location, upLocation); - position = upPosition; - } - - if (closestDown !== -1) { - const downPosition = this.getPosFromPixels(lineDivs[closestDown], location.x)!; - const downLocation = this.getPositionLocation(downPosition)!; - const downDistance = ui.distanceSquared(location, downLocation); - - if (downDistance < distance) { - distance = downDistance; - position = downPosition; - } - } - - return position; - } - - private checkRow( - lineDiv: ILineDiv, - fn: (lineDiv: ILineDiv) => ILineDiv | undefined, - rev?: boolean, - ): ILineDiv | undefined { - let _lineDiv: ILineDiv | undefined = lineDiv; - let rowDiv = _lineDiv as IRowDiv; - let oldRowDiv: IRowDiv | undefined; - while (rowDiv && rowDiv !== oldRowDiv && rowDiv.rowView) { - oldRowDiv = rowDiv; - _lineDiv = undefined; - for (const cell of rowDiv.rowView.cells) { - if (cell.div) { - const innerDiv = this.lineDivSelect( - fn, - (cell as ICellView).viewport.div, - true, - rev, - ); - if (innerDiv) { - _lineDiv = innerDiv; - rowDiv = innerDiv as IRowDiv; - break; - } - } - } - } - return _lineDiv; - } - - public lineDivSelect( - fn: (lineDiv: ILineDiv) => ILineDiv | undefined, - viewportDiv: IViewportDiv, - dive = false, - rev?: boolean, - ) { - if (rev) { - let elm = viewportDiv.lastElementChild as ILineDiv; - while (elm) { - if (elm.linePos !== undefined) { - let lineDiv = fn(elm); - if (lineDiv) { - if (dive) { - lineDiv = this.checkRow(lineDiv, fn, rev); - } - return lineDiv; - } - } - elm = elm.previousElementSibling as ILineDiv; - } - } else { - let elm = viewportDiv.firstElementChild as ILineDiv; - while (elm) { - if (elm.linePos !== undefined) { - let lineDiv = fn(elm); - if (lineDiv) { - if (dive) { - lineDiv = this.checkRow(lineDiv, fn, rev); - } - return lineDiv; - } - } else { - console.log(`elm in fwd line search is ${elm.tagName}`); - } - elm = elm.nextElementSibling as ILineDiv; - } - } - } - - private clickSpan(x: number, y: number, elm: HTMLSpanElement) { - const span = elm as ISegSpan; - const elmOff = pointerToElementOffsetWebkit(x, y); - if (elmOff) { - let computed = elmOffToSegOff(elmOff, span); - if (span.offset) { - computed += span.offset; - } - this.cursor.pos = span.segPos! + computed; - this.cursor.enable(); - if (this.childCursor) { - this.childCursor.leave(CursorDirection.Airlift); - this.childCursor = undefined; - } - const tilePos = findTile(this.sharedString, this.cursor.pos, "pg", false); - if (tilePos) { - this.curPG = tilePos.tile as MergeTree.Marker; - } - this.broadcastPresence(); - this.cursor.updateView(this); - if (this.parentFlow) { - this.parentFlow.focusChild = this; - } - this.focusChild = undefined; - return true; - } - } - - private getSegSpan(span: ISegSpan): ISegSpan | undefined { - let _span = span; - while (_span.tagName === "SPAN") { - if (_span.segPos) { - return _span; - } else { - _span = _span.parentElement as ISegSpan; - } - } - } - - private getPosFromPixels(targetLineDiv: ILineDiv | undefined, x: number) { - let position: number | undefined; - - if (targetLineDiv && targetLineDiv.linePos !== undefined) { - const targetLineBounds = targetLineDiv.getBoundingClientRect(); - const y = targetLineBounds.top + Math.floor(targetLineBounds.height / 2); - const elm = document.elementFromPoint(x, y)!; - if (elm.tagName === "DIV") { - if (targetLineDiv.lineEnd! - targetLineDiv.linePos === 1) { - // Empty line - position = targetLineDiv.linePos; - } else if (targetLineDiv === elm) { - if (targetLineDiv.indentWidth !== undefined) { - const relX = x - targetLineBounds.left; - position = - relX <= targetLineDiv.indentWidth - ? targetLineDiv.linePos - : targetLineDiv.lineEnd; - } else { - position = targetLineDiv.lineEnd; - } - } else { - // Content div - position = - x <= targetLineBounds.left ? targetLineDiv.linePos : targetLineDiv.lineEnd; - } - } else if (elm.tagName === "SPAN") { - const span = this.getSegSpan(elm as ISegSpan); - if (span) { - const elmOff = pointerToElementOffsetWebkit(x, y); - if (elmOff) { - let computed = elmOffToSegOff(elmOff, span); - if (span.offset) { - computed += span.offset; - } - position = span.segPos! + computed; - if (position === targetLineDiv.lineEnd) { - position--; - } - } - } else { - position = 0; - } - } - } - - return position; - } - - // TODO: handle symbol div - private setCursorPosFromPixels(targetLineDiv: ILineDiv | undefined, x: number) { - const position = this.getPosFromPixels(targetLineDiv, x); - if (position !== undefined) { - this.cursor.enable(); - if (this.childCursor) { - this.childCursor.leave(CursorDirection.Airlift); - this.childCursor = undefined; - } - this.cursor.pos = position; - return true; - } else { - return false; - } - } - - private getCanonicalX() { - const rect = this.cursor.rect(); - let x: number; - if (this.lastVerticalX >= 0) { - x = this.lastVerticalX; - } else { - x = Math.floor(rect.left); - this.lastVerticalX = x; - } - return x; - } - - private cursorRev(skipFirstRev = false) { - if (this.cursor.pos > FlowView.docStartPosition) { - if (!skipFirstRev) { - this.cursor.pos--; - } - const segoff = getContainingSegment(this.sharedString, this.cursor.pos); - if (segoff.segment && MergeTree.Marker.is(segoff.segment)) { - const marker = segoff.segment; - if (marker.refType & MergeTree.ReferenceType.Tile) { - if (refHasTileLabel(marker, "pg")) { - if ( - refHasRangeLabel(marker, "table") && - marker.refType & MergeTree.ReferenceType.NestEnd - ) { - this.cursorRev(); - } - } - } else if ( - marker.refType === MergeTree.ReferenceType.NestEnd && - refHasRangeLabel(marker, "cell") - ) { - const cellMarker = marker as Table.ICellMarker; - const endId = cellMarker.getId(); - let beginMarker: Table.ICellMarker | undefined; - if (endId) { - const id = Table.idFromEndId(endId); - beginMarker = this.sharedString.getMarkerFromId(id) as Table.ICellMarker; - } - if (beginMarker && Table.cellIsMoribund(beginMarker)) { - this.tryMoveCell(this.cursor.pos, true); - } else { - this.cursorRev(); - } - } else { - this.cursorRev(); - } - } - } - } - - private cursorFwd() { - if (this.cursor.pos < this.sharedString.getLength() - 1) { - this.cursor.pos++; - - const segoff = this.sharedString.getContainingSegment(this.cursor.pos); - if (segoff.segment && MergeTree.Marker.is(segoff.segment)) { - // REVIEW: assume marker for now - const marker = segoff.segment; - if (marker.refType & MergeTree.ReferenceType.Tile) { - if (refHasTileLabel(marker, "pg")) { - if ( - refHasRangeLabel(marker, "table") && - marker.refType & MergeTree.ReferenceType.NestEnd - ) { - this.cursorFwd(); - } else { - return; - } - } - } else if (marker.refType & MergeTree.ReferenceType.NestBegin) { - if (refHasRangeLabel(marker, "table")) { - this.cursor.pos += 3; - } else if (refHasRangeLabel(marker, "row")) { - this.cursor.pos += 2; - } else if (refHasRangeLabel(marker, "cell")) { - if (Table.cellIsMoribund(marker)) { - this.tryMoveCell(this.cursor.pos); - } else { - this.cursor.pos += 1; - } - } else { - this.cursorFwd(); - } - } else if (marker.refType & MergeTree.ReferenceType.NestEnd) { - if (refHasRangeLabel(marker, "row")) { - this.cursorFwd(); - } else if (refHasRangeLabel(marker, "table")) { - this.cursor.pos += 2; - } else { - this.cursorFwd(); - } - } else { - this.cursorFwd(); - } - } - } - } - - private verticalMove(lineCount: number) { - const up = lineCount < 0; - const lineDiv = this.cursor.lineDiv(); - let targetLineDiv: ILineDiv | undefined = lineDiv; - if (lineCount < 0) { - do { - targetLineDiv = targetLineDiv.previousElementSibling as ILineDiv; - } while (targetLineDiv && targetLineDiv.linePos === undefined); - } else { - do { - targetLineDiv = targetLineDiv.nextElementSibling as ILineDiv; - } while (targetLineDiv && targetLineDiv.linePos === undefined); - } - const x = this.getCanonicalX(); - - // If line div is row, then find line in box closest to x - function checkInTable() { - let rowDiv = targetLineDiv as IRowDiv; - while (rowDiv && rowDiv.rowView) { - if (rowDiv.rowView) { - const cell = rowDiv.rowView.findClosestCell(x) as ICellView; - if (cell) { - targetLineDiv = up - ? cell.viewport.lastLineDiv() - : cell.viewport.firstLineDiv(); - rowDiv = targetLineDiv as IRowDiv; - } else { - break; - } - } - } - } - - if (targetLineDiv) { - checkInTable(); - return this.setCursorPosFromPixels(targetLineDiv, x); - } else { - // TODO: handle nested tables - // go out to row containing this line (line may be at top or bottom of box) - const rowDiv = findRowParent(lineDiv); - if (rowDiv && rowDiv.rowView) { - const rowView = rowDiv.rowView; - const tableView = rowView.table!; - const targetRow: Table.Row | undefined = up - ? tableView.findPrecedingRow(rowView) - : tableView.findNextRow(rowView); - if (targetRow) { - const cell = targetRow.findClosestCell(x) as ICellView; - if (cell) { - targetLineDiv = up - ? cell.viewport.lastLineDiv() - : cell.viewport.firstLineDiv(); - } - return this.setCursorPosFromPixels(targetLineDiv, x); - } else { - // Top or bottom row of table - targetLineDiv = up - ? (rowDiv.previousElementSibling as ILineDiv) - : (rowDiv.nextElementSibling as ILineDiv); - if (targetLineDiv) { - checkInTable(); - return this.setCursorPosFromPixels(targetLineDiv, x); - } - } - } - } - } - - private viewportCharCount() { - return this.viewportEndPos! - this.viewportStartPos!; - } - - private clearSelection(render = true) { - // TODO: only rerender line if selection on one line - if (this.cursor.getSelection()) { - this.cursor.clearSelection(); - this.broadcastPresence(); - if (render) { - this.hostSearchMenu(this.cursor.pos); - } - } - } - - public setEdit() { - window.oncontextmenu = preventD; - this.element.onmousemove = preventD; - this.element.onmouseup = preventD; - // TODO onmousewheel does not appear on DOM d.ts - (this.element as any).onselectstart = preventD; - let prevX = Nope; - let prevY = Nope; - let freshDown = false; - - const moveObjects = (e: MouseEvent, fresh = false) => { - if (e.button === 0) { - prevX = e.clientX; - prevY = e.clientY; - const elm = document.elementFromPoint(prevX, prevY); - if (elm) { - const span = elm as ISegSpan; - const segspan: ISegSpan = span.seg ? span : (span.parentElement as ISegSpan); - if (segspan && segspan.seg) { - this.clickSpan(e.clientX, e.clientY, segspan); - } - } - } - }; - - const mousemove = (e: MouseEvent) => { - if (e.button === 0) { - if (prevX !== e.clientX || prevY !== e.clientY) { - if (freshDown) { - this.cursor.tryMark(); - freshDown = false; - } - moveObjects(e); - } - e.preventDefault(); - e.returnValue = false; - return false; - } - }; - - this.element.onmousedown = (e) => { - this.element.focus(); - if (e.button === 0) { - freshDown = true; - moveObjects(e, true); - if (!e.shiftKey) { - this.clearSelection(); - } - this.element.onmousemove = mousemove; - } - e.stopPropagation(); - e.preventDefault(); - e.returnValue = false; - return false; - }; - - this.element.onmouseup = (e) => { - this.element.onmousemove = preventD; - if (e.button === 0) { - freshDown = false; - const elm = document.elementFromPoint(prevX, prevY); - const span = elm as ISegSpan; - const segspan: ISegSpan = span.seg ? span : (span.parentElement as ISegSpan); - if (segspan && segspan.seg) { - this.clickSpan(e.clientX, e.clientY, segspan); - if (this.cursor.emptySelection()) { - this.clearSelection(); - } - } - e.stopPropagation(); - e.preventDefault(); - e.returnValue = false; - return false; - } else if (e.button === 2) { - e.preventDefault(); - e.returnValue = false; - return false; - } - }; - - this.element.onblur = (e) => { - // TODO: doesn't actually stop timer. - this.cursor.hide(); - }; - - this.element.onfocus = (e) => { - // TODO: doesn't actually start timer. - this.cursor.show(); - }; - - // TODO onmousewheel does not appear on DOM d.ts - (this.element as any).onmousewheel = (e) => { - if (!this.wheelTicking) { - const factor = 20; - let inputDelta = e.wheelDelta; - inputDelta = Math.abs(e.wheelDelta) === 120 ? e.wheelDelta / 6 : e.wheelDelta / 2; - const delta = factor * inputDelta; - // console.log(`top char: ${this.topChar - delta} factor ${factor}; delta: ${delta} wheel: ${e.wheelDeltaY} ${e.wheelDelta} ${e.detail}`); - setTimeout(() => { - this.render(Math.floor(this.topChar - delta)); - this.apresScroll(delta < 0); - this.wheelTicking = false; - }, 20); - this.wheelTicking = true; - } - e.stopPropagation(); - e.preventDefault(); - e.returnValue = false; - }; - - /* eslint-disable unicorn/prefer-switch */ - // The logic below is complex enough that using switches makes the code far less readable. - const keydownHandler = (e: KeyboardEvent) => { - if (this.focusChild) { - this.focusChild.keydownHandler!(e); - } else if (!this.activeCommandBox) { - const saveLastVertX = this.lastVerticalX; - let specialKey = true; - this.lastVerticalX = -1; - if (e.ctrlKey && e.keyCode !== 17) { - this.keyCmd(e.keyCode, e.shiftKey); - } else if (e.keyCode === KeyCode.TAB) { - this.onTAB(e.shiftKey); - } else if (e.keyCode === KeyCode.esc) { - this.clearSelection(); - } else if (e.keyCode === KeyCode.backspace) { - let toRemove = this.cursor.getSelection(); - if (toRemove) { - // If there was a selected range, use it as range to remove below. In preparation, clear - // the FlowView's selection and set the cursor to the start of the range to be deleted. - this.clearSelection(); - this.cursor.pos = toRemove.start; - } else { - // Otherwise, construct the range to remove by moving the cursor once in the reverse - // direction. Below we will remove the positions spanned by the current and previous cursor - // positions. - const removeEnd = this.cursor.pos; - this.cursorRev(); - toRemove = { - end: removeEnd, - start: this.cursor.pos, - }; - } - this.sharedString.removeText(toRemove.start, toRemove.end); - } else if ( - (e.keyCode === KeyCode.pageUp || e.keyCode === KeyCode.pageDown) && - !this.ticking - ) { - setTimeout(() => { - this.scroll(e.keyCode === KeyCode.pageUp); - this.ticking = false; - }, 20); - this.ticking = true; - } else if (e.keyCode === KeyCode.home) { - this.cursor.pos = FlowView.docStartPosition; - this.render(FlowView.docStartPosition); - } else if (e.keyCode === KeyCode.end) { - const halfport = Math.floor(this.viewportCharCount() / 2); - const topChar = this.sharedString.getLength() - halfport; - this.cursor.pos = topChar; - this.broadcastPresence(); - this.render(topChar); - } else if (e.keyCode === KeyCode.rightArrow) { - this.undoRedoManager.closeCurrentOperation(); - if (this.cursor.pos < this.sharedString.getLength() - 1) { - if (this.cursor.pos === this.viewportEndPos) { - this.scroll(false, true); - } - if (e.shiftKey) { - this.cursor.tryMark(); - } else { - this.clearSelection(); - } - this.cursorFwd(); - this.broadcastPresence(); - this.cursor.updateView(this); - } - } else if (e.keyCode === KeyCode.leftArrow) { - this.undoRedoManager.closeCurrentOperation(); - if (this.cursor.pos > FlowView.docStartPosition) { - if (this.cursor.pos === this.viewportStartPos) { - this.scroll(true, true); - } - if (e.shiftKey) { - this.cursor.tryMark(); - } else { - this.clearSelection(); - } - this.cursorRev(); - this.broadcastPresence(); - this.cursor.updateView(this); - } - } else if (e.keyCode === KeyCode.upArrow || e.keyCode === KeyCode.downArrow) { - this.undoRedoManager.closeCurrentOperation(); - this.lastVerticalX = saveLastVertX; - let lineCount = 1; - if (e.keyCode === KeyCode.upArrow) { - lineCount = -1; - } - if (e.shiftKey) { - this.cursor.tryMark(); - } else { - this.clearSelection(); - } - const maxPos = this.sharedString.getLength() - 1; - if (this.viewportEndPos! > maxPos) { - this.viewportEndPos = maxPos; - } - const vpEnd = this.viewportEndPos; - if (this.cursor.pos < maxPos || lineCount < 0) { - if (!this.verticalMove(lineCount)) { - if ( - (this.viewportStartPos! > 0 && lineCount < 0) || - (this.viewportEndPos! < maxPos && lineCount > 0) - ) { - this.scroll(lineCount < 0, true); - if (lineCount > 0) { - while (vpEnd === this.viewportEndPos) { - if (this.cursor.pos > maxPos) { - this.cursor.pos = maxPos; - break; - } - this.scroll(lineCount < 0, true); - } - } - this.verticalMove(lineCount); - } - } - if (this.cursor.pos > maxPos) { - this.cursor.pos = maxPos; - } - this.broadcastPresence(); - this.cursor.updateView(this); - } - } else if (!e.ctrlKey) { - specialKey = false; - } - if (specialKey) { - e.preventDefault(); - e.returnValue = false; - } - } - }; - /* eslint-enable unicorn/prefer-switch */ - - const keypressHandler = (e: KeyboardEvent) => { - if (this.focusChild) { - this.focusChild.keypressHandler!(e); - } else if (!this.activeCommandBox) { - const pos = this.cursor.pos; - const code = e.charCode; - if (code === CharacterCodes.cr) { - // TODO: other labels; for now assume only list/pg tile labels - this.insertParagraph(this.cursor.pos++); - } else { - this.sharedString.insertText(pos, String.fromCharCode(code)); - if (code === CharacterCodes.space) { - this.undoRedoManager.closeCurrentOperation(); - } - - this.clearSelection(); - } - } - }; - - // Register for keyboard messages - this.on("keydown", keydownHandler); - this.on("keypress", keypressHandler); - this.keypressHandler = keypressHandler; - this.keydownHandler = keydownHandler; - } - - private viewTileProps() { - let searchPos = this.cursor.pos; - if (this.cursor.pos === this.cursor.lineDiv().lineEnd) { - searchPos--; - } - const tileInfo = findTile(this.sharedString, searchPos, "pg", false); - if (tileInfo) { - let buf = ""; - if (tileInfo.tile.properties) { - // eslint-disable-next-line guard-for-in, no-restricted-syntax - for (const key in tileInfo.tile.properties) { - buf += ` { ${key}: ${tileInfo.tile.properties[key]} }`; - } - } - - const lc = !!(tileInfo.tile as Paragraph.IParagraphMarker).listCache; - console.log(`tile at pos ${tileInfo.pos} with props${buf} and list cache: ${lc}`); - } - } - - private setList(listKind = 0) { - this.undoRedoManager.closeCurrentOperation(); - const searchPos = this.cursor.pos; - const tileInfo = findTile(this.sharedString, searchPos, "pg", false); - if (tileInfo) { - const tile = tileInfo.tile as Paragraph.IParagraphMarker; - let listStatus = false; - if (refHasTileLabel(tile, "list")) { - listStatus = true; - } - const curLabels = tile.properties![MergeTree.reservedTileLabelsKey] as string[]; - - if (listStatus) { - const remainingLabels = curLabels.filter((l) => l !== "list"); - this.sharedString.annotateRange(tileInfo.pos, tileInfo.pos + 1, { - [MergeTree.reservedTileLabelsKey]: remainingLabels, - series: undefined, - }); - } else { - const augLabels = curLabels.slice(); - augLabels.push("list"); - let indentLevel = 1; - if (tile.properties && tile.properties.indentLevel) { - indentLevel = tile.properties.indentLevel; - } - this.sharedString.annotateRange(tileInfo.pos, tileInfo.pos + 1, { - [MergeTree.reservedTileLabelsKey]: augLabels, - indentLevel, - listKind, - }); - } - tile.listCache = undefined; - } - this.undoRedoManager.closeCurrentOperation(); - } - - private tryMoveCell(pos: number, shift = false) { - const cursorContext = this.sharedString.getStackContext(pos, ["table", "cell", "row"]); - if (cursorContext.table && !cursorContext.table.empty()) { - const tableMarker = cursorContext.table.top() as Table.ITableMarker; - const tableView = tableMarker.table!; - if (cursorContext.cell && !cursorContext.cell.empty()) { - const cell = cursorContext.cell.top() as Table.ICellMarker; - const toCell: Table.Cell | undefined = shift - ? tableView.prevcell(cell.cell!) - : tableView.nextcell(cell.cell!); - if (toCell) { - const position = this.sharedString.getPosition(toCell.marker); - this.cursor.pos = position + 1; - } else { - if (shift) { - const position = this.sharedString.getPosition(tableView.tableMarker); - this.cursor.pos = position - 1; - } else { - const endPosition = this.sharedString.getPosition(tableView.endTableMarker); - this.cursor.pos = endPosition + 1; - } - } - this.broadcastPresence(); - this.cursor.updateView(this); - } - return true; - } else { - return false; - } - } - - // TODO: tab stops in non-list, non-table paragraphs - private onTAB(shift = false) { - const searchPos = this.cursor.pos; - const tileInfo = findTile(this.sharedString, searchPos, "pg", false); - if (tileInfo) { - if (!this.tryMoveCell(tileInfo.pos, shift)) { - const tile = tileInfo.tile as Paragraph.IParagraphMarker; - this.increaseIndent(tile, tileInfo.pos, shift); - } - } - } - - private toggleBlockquote() { - const tileInfo = findTile(this.sharedString, this.cursor.pos, "pg", false); - if (tileInfo) { - const tile = tileInfo.tile; - const props = tile.properties; - this.undoRedoManager.closeCurrentOperation(); - if (props && props.blockquote) { - this.sharedString.annotateRange(tileInfo.pos, tileInfo.pos + 1, { - blockquote: false, - }); - } else { - this.sharedString.annotateRange(tileInfo.pos, tileInfo.pos + 1, { - blockquote: true, - }); - } - this.undoRedoManager.closeCurrentOperation(); - } - } - - private toggleBold() { - this.toggleWordOrSelection("fontWeight", "bold", undefined); - } - - private toggleItalic() { - this.toggleWordOrSelection("fontStyle", "italic", "normal"); - } - - private toggleUnderline() { - this.toggleWordOrSelection("textDecoration", "underline", undefined); - } - - private copyFormat() { - const segoff = getContainingSegment(this.sharedString, this.cursor.pos); - if (segoff.segment && MergeTree.TextSegment.is(segoff.segment)) { - this.formatRegister = MergeTree.extend( - MergeTree.createMap(), - segoff.segment.properties, - ); - } - } - - private setProps(props: MergeTree.PropertySet) { - const sel = this.cursor.getSelection(); - this.undoRedoManager.closeCurrentOperation(); - if (sel) { - this.clearSelection(false); - this.sharedString.annotateRange(sel.start, sel.end, props); - } else { - const wordRange = getCurrentWord(this.cursor.pos, this.sharedString); - if (wordRange) { - this.sharedString.annotateRange(wordRange.wordStart, wordRange.wordEnd, props); - } - } - this.undoRedoManager.closeCurrentOperation(); - } - - private paintFormat() { - if (this.formatRegister) { - this.setProps(this.formatRegister); - } - } - - private setFont(family: string, size = "18px") { - this.setProps({ fontFamily: family, fontSize: size }); - } - - private setColor(color: string) { - this.setProps({ color }); - } - - private toggleWordOrSelection(name: string, valueOn: string, valueOff: string | undefined) { - const sel = this.cursor.getSelection(); - if (sel) { - this.clearSelection(false); - this.toggleRange(name, valueOn, valueOff, sel.start, sel.end); - } else { - const wordRange = getCurrentWord(this.cursor.pos, this.sharedString); - if (wordRange) { - this.toggleRange(name, valueOn, valueOff, wordRange.wordStart, wordRange.wordEnd); - } - } - } - - private toggleRange( - name: string, - valueOn: string, - valueOff: string | undefined, - start: number, - end: number, - ) { - let someSet = false; - const findPropSet = (segment: MergeTree.ISegment) => { - if (MergeTree.TextSegment.is(segment)) { - if (segment.properties && segment.properties[name] === valueOn) { - someSet = true; - } - return !someSet; - } - }; - this.sharedString.walkSegments( - findPropSet as (segment: MergeTree.ISegment) => boolean, - start, - end, - ); - this.undoRedoManager.closeCurrentOperation(); - if (someSet) { - this.sharedString.annotateRange(start, end, { [name]: valueOff }); - } else { - this.sharedString.annotateRange(start, end, { [name]: valueOn }); - } - this.undoRedoManager.closeCurrentOperation(); - } - - private deleteRow() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const rowMarker = stack.row.top() as Table.IRowMarker; - if (!tableMarker.table) { - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.deleteRow(this.sharedString, rowMarker.row!, tableMarker.table!); - } - } - - public deleteCellShiftLeft() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const cellMarker = stack.cell.top() as Table.ICellMarker; - if (!tableMarker.table) { - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.deleteCellShiftLeft(this.sharedString, cellMarker.cell!, tableMarker.table!); - } - } - - private deleteColumn() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const rowMarker = stack.row.top() as Table.IRowMarker; - const cellMarker = stack.cell.top() as Table.ICellMarker; - if (!tableMarker.table) { - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.deleteColumn( - this.sharedString, - this.runtime.clientId!, - cellMarker.cell!, - rowMarker.row!, - tableMarker.table!, - ); - } - } - - private insertRow() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const rowMarker = stack.row.top() as Table.IRowMarker; - if (!tableMarker.table) { - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.insertRow( - this.sharedString, - this.runtime.clientId!, - rowMarker.row!, - tableMarker.table!, - ); - } - } - - private tableSummary() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - if (!tableMarker.table) { - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.succinctPrintTable(tableMarker, tableMarkerPos, this.sharedString); - } - } - - private insertColumn() { - const stack = this.sharedString.getStackContext(this.cursor.pos, ["table", "cell", "row"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - const rowMarker = stack.row.top() as Table.IRowMarker; - const cellMarker = stack.cell.top() as Table.ICellMarker; - if (!tableMarker.table) { - const tableMarkerPos = getPosition(this.sharedString, tableMarker); - Table.parseTable( - tableMarker, - tableMarkerPos, - this.sharedString, - makeFontInfo(this.lastDocContext!), - ); - } - Table.insertColumn( - this.sharedString, - this.runtime.clientId!, - cellMarker.cell!, - rowMarker.row!, - tableMarker.table!, - ); - } - } - - private setPGProps(props: MergeTree.PropertySet) { - const tileInfo = findTile(this.sharedString, this.cursor.pos, "pg", false); - if (tileInfo) { - const pgMarker = tileInfo.tile as Paragraph.IParagraphMarker; - this.sharedString.annotateRange( - tileInfo.pos, - pgMarker.cachedLength + tileInfo.pos, - props, - ); - Paragraph.clearContentCaches(pgMarker); - } - } - - private selectAll() { - this.cursor.clearSelection(); - this.cursor.mark = 0; - this.cursor.pos = this.sharedString.getLength(); - } - - private keyCmd(charCode: number, shift = false) { - switch (charCode) { - case CharacterCodes.A: - this.selectAll(); - break; - case CharacterCodes.R: { - this.updatePGInfo(this.cursor.pos - 1); - Table.createTable(this.cursor.pos, this.sharedString, this.runtime.clientId!); - break; - } - case CharacterCodes.M: { - this.activeCommandBox = true; - this.showCommandBox(); - break; - } - case CharacterCodes.L: - this.setList(); - break; - case CharacterCodes.B: { - this.toggleBold(); - break; - } - case CharacterCodes.I: { - this.toggleItalic(); - break; - } - case CharacterCodes.U: { - this.toggleUnderline(); - break; - } - case CharacterCodes.D: - this.setList(1); - break; - case CharacterCodes.G: - this.viewTileProps(); - this.hostSearchMenu(this.cursor.pos); - break; - case CharacterCodes.Y: - this.undoRedoManager.undoOperation(); - break; - case CharacterCodes.Z: - this.undoRedoManager.redoOperation(); - break; - default: - console.log(`got command key ${String.fromCharCode(charCode)} code: ${charCode}`); - break; - } - } - - private preScroll() { - if (this.lastVerticalX === -1) { - const rect = this.cursor.rect(); - this.lastVerticalX = rect.left; - } - } - - private apresScroll(up: boolean) { - if (this.cursor.pos < this.viewportStartPos! || this.cursor.pos >= this.viewportEndPos!) { - const x = this.getCanonicalX(); - if (up) { - this.setCursorPosFromPixels(this.firstLineDiv(), x); - } else { - this.setCursorPosFromPixels(this.lastLineDiv(), x); - } - this.broadcastPresence(); - this.cursor.updateView(this); - } - } - - private scroll(up: boolean, one = false) { - let scrollTo = this.topChar; - if (one) { - if (up) { - const firstLineDiv = this.firstLineDiv()!; - scrollTo = firstLineDiv.linePos! - 2; - if (scrollTo < 0) { - return; - } - } else { - const nextFirstLineDiv = this.firstLineDiv()!.nextElementSibling as ILineDiv; - if (nextFirstLineDiv) { - scrollTo = nextFirstLineDiv.linePos!; - } else { - return; - } - } - } else { - const len = this.sharedString.getLength(); - const halfport = Math.floor(this.viewportCharCount() / 2); - if ((up && this.topChar === 0) || (!up && this.topChar > len - halfport)) { - return; - } - if (up) { - scrollTo -= halfport; - } else { - scrollTo += halfport; - } - if (scrollTo >= len) { - scrollTo = len - 1; - } - } - this.preScroll(); - this.render(scrollTo); - this.apresScroll(up); - } - - public render(topChar?: number, changed = false) { - const len = this.sharedString.getLength(); - if (len === 0) { - return; - } - if (topChar !== undefined) { - if ((this.topChar === topChar || (this.topChar === -1 && topChar < 0)) && !changed) { - return; - } - this.topChar = topChar; - if (this.topChar < 0) { - this.topChar = 0; - } - if (this.topChar >= len) { - this.topChar = len - this.viewportCharCount() / 2; - } - } - - // TODO: consider using markers for presence info once splice segments during pg render - this.updatePresencePositions(); - domutils.clearSubtree(this.viewportDiv); - // This.viewportDiv.appendChild(this.cursor.editSpan); - const renderOutput = renderTree(this.viewportDiv, this.topChar, this); - this.viewportStartPos = renderOutput.viewportStartPos; - this.viewportEndPos = renderOutput.viewportEndPos; - - this.emit("render", { - range: { min: 1, max: this.sharedString.getLength(), value: this.viewportStartPos }, - viewportEndPos: this.viewportEndPos, - viewportStartPos: this.viewportStartPos, - }); - } - - public loadFinished(clockStart = 0) { - this.render(0, true); - if (clockStart > 0) { - console.log( - `time to edit/impression: ${this.timeToEdit} time to load: ${ - Date.now() - clockStart - }ms len: ${this.sharedString.getLength()} - ${performance.now()}`, - ); - } - - // Set up for presence carets - this.runtime.on("signal", (message: IInboundSignalMessage, local: boolean) => { - if (message.type === presenceSignalType) { - this.remotePresenceUpdate(message, local); - } - }); - this.broadcastPresence(); - - this.sharedString.on("valueChanged", (delta: types.IValueChanged) => { - this.queueRender(undefined, true); - }); - } - - private updateTableInfo(changePos: number) { - const stack = this.sharedString.getStackContext(changePos, ["table"]); - if (stack.table && !stack.table.empty()) { - const tableMarker = stack.table.top() as Table.ITableMarker; - tableMarker.table = undefined; - } - } - - private updatePGInfo(changePos: number) { - const tileInfo = findTile(this.sharedString, changePos, "pg", false); - if (tileInfo) { - const tile = tileInfo.tile as Paragraph.IParagraphMarker; - Paragraph.clearContentCaches(tile); - } else { - console.log("did not find pg to clear"); - } - if (this.modifiedMarkers.length > 0) { - this.updateTableInfo(changePos); - } - } - - public hostSearchMenu(updatePos: number) { - if (this.parentFlow) { - this.parentFlow.hostSearchMenu(updatePos); - } else { - if (updatePos >= 0) { - this.updatePGInfo(updatePos); - } - if (!this.pendingRender) { - this.pendingRender = true; - window.requestAnimationFrame(() => { - this.pendingRender = false; - this.render(this.topChar, true); - }); - } - } - } - - protected resizeCore(bounds: ui.Rectangle) { - this.viewportRect = bounds.inner(0.92); - if (this.viewportRect.height >= 0) { - ui.Rectangle.conformElementToRect(this.viewportDiv, this.viewportRect); - if (this.sharedString.getLength() > 0) { - this.render(this.topChar, true); - } - if (this.viewportDiv.style.backgroundSize !== undefined) { - const rect = this.viewportDiv.getBoundingClientRect(); - this.viewportDiv.style.backgroundSize = `${rect.width}px ${rect.height}px`; - } - } - } - - private insertParagraph(pos: number) { - const curTilePos = findTile(this.sharedString, pos, "pg", false); - const pgMarker = curTilePos?.tile as Paragraph.IParagraphMarker; - const pgPos = curTilePos?.pos ?? 0; - Paragraph.clearContentCaches(pgMarker); - const curProps = pgMarker.properties!; - const newProps = MergeTree.createMap(); - const newLabels = ["pg"]; - - // TODO: Should merge w/all existing tile labels? - if (Paragraph.isListTile(pgMarker)) { - newLabels.push("list"); - newProps.indentLevel = curProps.indentLevel; - newProps.listKind = curProps.listKind; - } - - newProps[MergeTree.reservedTileLabelsKey] = newLabels; - if (this.srcLanguage !== "en") { - newProps.fromLanguage = this.srcLanguage; - } - // TODO: place in group op - // old marker gets new props - this.sharedString.annotateRange(pgPos, pgPos + 1, newProps, { name: "rewrite" }); - // New marker gets existing props - this.sharedString.insertMarker(pos, MergeTree.ReferenceType.Tile, curProps); - this.undoRedoManager.closeCurrentOperation(); - } - - private remotePresenceUpdate(message: IInboundSignalMessage, local: boolean) { - if (local) { - return; - } - - const remotePresenceBase = message.content as IRemotePresenceBase; - - if (remotePresenceBase.type === "selection") { - this.remotePresenceToLocal( - message.clientId!, - remotePresenceBase as IRemotePresenceInfo, - ); - } - } - - private remotePresenceFromEdit( - clientId: string, - refseq: number, - oldpos: number, - posAdjust = 0, - ) { - const remotePosInfo: IRemotePresenceInfo = { - origMark: -1, - origPos: oldpos + posAdjust, - refseq, - type: "selection", - }; - - this.remotePresenceToLocal(clientId, remotePosInfo); - } - - private remotePresenceToLocal( - clientId: string, - remotePresenceInfo: IRemotePresenceInfo, - posAdjust = 0, - ) { - const rempos = this.sharedString.resolveRemoteClientPosition( - remotePresenceInfo.origPos, - remotePresenceInfo.refseq, - clientId, - )!; - const segoff = this.sharedString.getContainingSegment(rempos); - - if (segoff.segment) { - const clientInfo = this.getRemoteClientInfo(clientId); - if (clientInfo) { - const localPresenceInfo = { - clientId, - fresh: true, - localRef: this.sharedString.createLocalReferencePosition( - segoff.segment, - segoff.offset!, - MergeTree.ReferenceType.SlideOnRemove, - undefined, - ), - presenceColor: this.presenceVector.has(clientId) - ? this.presenceVector.get(clientId)!.presenceColor - : presenceColors[this.presenceVector.size % presenceColors.length], - shouldShowCursor: () => - this.runtime.clientId !== clientId && - this.getRemoteClientInfo(clientId) !== undefined, - user: clientInfo.user, - } as ILocalPresenceInfo; - if (remotePresenceInfo.origMark >= 0) { - const markSegoff = this.sharedString.getContainingSegment( - remotePresenceInfo.origMark, - ); - if (markSegoff.segment) { - localPresenceInfo.markLocalRef = - this.sharedString.createLocalReferencePosition( - markSegoff.segment, - markSegoff.offset!, - MergeTree.ReferenceType.SlideOnRemove, - undefined, - ); - } - } - this.updatePresenceVector(localPresenceInfo); - } - } - } - - private getRemoteClientInfo(clientId: string): IClient | undefined { - const quorumClient = this.runtime.getQuorum().getMember(clientId); - if (quorumClient) { - return quorumClient.client; - } else { - const audience = this.runtime.getAudience().getMembers(); - return audience.get(clientId); - } - } - - private broadcastPresence() { - if (this.runtime.connected) { - const presenceInfo: IRemotePresenceInfo = { - origMark: this.cursor.mark, - origPos: this.cursor.pos, - refseq: this.sharedString.getCurrentSeq(), - type: "selection", - }; - this.runtime.submitSignal(presenceSignalType, presenceInfo); - } - } - - private increaseIndent(tile: Paragraph.IParagraphMarker, pos: number, decrease = false) { - tile.listCache = undefined; - this.undoRedoManager.closeCurrentOperation(); - if (decrease && tile.properties!.indentLevel > 0) { - this.sharedString.annotateRange( - pos, - pos + 1, - { indentLevel: -1 }, - { name: "incr", defaultValue: 1, minValue: 0 }, - ); - } else if (!decrease) { - this.sharedString.annotateRange( - pos, - pos + 1, - { indentLevel: 1 }, - { name: "incr", defaultValue: 0 }, - ); - } - this.undoRedoManager.closeCurrentOperation(); - } - - private handleSharedStringDelta( - event: Sequence.SequenceDeltaEvent, - target: Sequence.SharedString, - ) { - let opCursorPos: number | undefined; - event.ranges.forEach((range) => { - if (MergeTree.Marker.is(range.segment)) { - this.updatePGInfo(range.position - 1); - } else if (MergeTree.TextSegment.is(range.segment)) { - if (range.operation === MergeTree.MergeTreeDeltaType.REMOVE) { - opCursorPos = range.position; - } else { - const insertOrAnnotateEnd = range.position + range.segment.cachedLength; - this.updatePGInfo(insertOrAnnotateEnd); - if (range.operation === MergeTree.MergeTreeDeltaType.INSERT) { - opCursorPos = insertOrAnnotateEnd; - } - } - } - // If it was a remote op before the local cursor, we need to adjust - // the local cursor - if (!event.isLocal && range.position <= this.cursor.pos) { - let adjust = range.segment.cachedLength; - // We might not need to use the full length if - // the range crosses the curors position - if (range.position + adjust > this.cursor.pos) { - adjust -= range.position + adjust - this.cursor.pos; - } - - // Do nothing for annotate, as it doesn't affect position - if (range.operation === MergeTree.MergeTreeDeltaType.REMOVE) { - this.cursor.pos -= adjust; - } else if (range.operation === MergeTree.MergeTreeDeltaType.INSERT) { - this.cursor.pos += adjust; - } - } - }); - - if (event.isLocal) { - if (opCursorPos !== undefined) { - this.cursor.pos = opCursorPos; - } - this.hostSearchMenu(this.cursor.pos); - } else { - if (opCursorPos !== undefined) { - this.remotePresenceFromEdit( - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - event.opArgs.sequencedMessage!.clientId as string, - event.opArgs.sequencedMessage!.referenceSequenceNumber, - opCursorPos, - ); - } - this.queueRender( - undefined, - this.posInViewport(event.first.position) || this.posInViewport(opCursorPos!), - ); - } - } - - private posInViewport(pos: number) { - return this.viewportEndPos! > pos && pos >= this.viewportStartPos!; - } - - private presenceQueueRender(localPresenceInfo: ILocalPresenceInfo, sameLine = false) { - if ( - !this.pendingRender && - (this.posInViewport(localPresenceInfo.xformPos!) || - this.posInViewport(localPresenceInfo.markXformPos!)) - ) { - if (!sameLine) { - this.pendingRender = true; - window.requestAnimationFrame(() => { - this.pendingRender = false; - this.render(this.topChar, true); - }); - } else { - reRenderLine(localPresenceInfo.cursor!.lineDiv(), this); - } - } - } - - private queueRender(msg: ISequencedDocumentMessage | undefined, go = false) { - if (!this.pendingRender && (go || (msg && msg.contents))) { - this.pendingRender = true; - window.requestAnimationFrame(() => { - this.pendingRender = false; - this.render(this.topChar, true); - }); - } - } -} - -/* eslint-enable @typescript-eslint/no-non-null-assertion */ -/* eslint-enable @typescript-eslint/consistent-type-assertions */ -/* eslint-enable @typescript-eslint/strict-boolean-expressions */ -/* eslint-enable no-bitwise */ diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/index.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/index.ts deleted file mode 100644 index d41924e89efd..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export { DockPanel } from "./dockPanel"; -export { FlowContainer } from "./flowContainer"; -export { FlowView, IDocumentContext } from "./flowView"; -export { - CursorDirection, - IProvideViewCursor, - IProvideViewLayout, - IViewCursor, - IViewLayout, -} from "./layout"; -// eslint-disable-next-line import/no-internal-modules -export { CharacterCodes } from "../text/characterCodes"; -export { KeyCode } from "./keycode"; -export { clearSubtree, getLineHeight, getTextHeight, getTextWidth } from "./domutils"; -export { Title } from "./title"; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/keycode.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/keycode.ts deleted file mode 100644 index 406fbc6461e2..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/keycode.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export enum KeyCode { - backspace = 8, - TAB = 9, - esc = 27, - pageUp = 33, - pageDown = 34, - end = 35, - home = 36, - leftArrow = 37, - upArrow = 38, - rightArrow = 39, - downArrow = 40, - del = 46, - letter_a = 65, - letter_z = 90, -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/layout.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/layout.ts deleted file mode 100644 index af960efbab31..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/layout.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export const IViewLayout: keyof IProvideViewLayout = "IViewLayout"; - -export interface IProvideViewLayout { - readonly IViewLayout: IViewLayout; -} - -/** - * Provide information about component preferences for layout. - */ -export interface IViewLayout extends IProvideViewLayout { - aspectRatio?: number; - minimumWidth?: number; - minimumHeight?: number; - variableHeight?: boolean; - requestedWidthPercentage?: number; - canInline?: boolean; - preferInline?: boolean; -} - -/** - * Direction from which the cursor has entered or left a component. - */ -export enum CursorDirection { - Left, - Right, - Up, - Down, - Airlift, - Focus, -} - -export const IViewCursor: keyof IProvideViewCursor = "IViewCursor"; - -export interface IProvideViewCursor { - readonly IViewCursor: IViewCursor; -} - -export interface IViewCursor extends IProvideViewCursor { - enter(direction: CursorDirection): void; - leave(direction: CursorDirection): void; - // Returns true if cursor leaves the component - fwd(): boolean; - rev(): boolean; -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/controls/title.ts b/examples/data-objects/shared-text/src/client-ui-lib/controls/title.ts deleted file mode 100644 index 69b0d16f174d..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/title.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable no-bitwise */ - -import * as ui from "../ui"; - -export class Title extends ui.Component { - public viewportDiv: HTMLDivElement; - - constructor(element: HTMLDivElement) { - super(element); - this.viewportDiv = document.createElement("div"); - this.element.appendChild(this.viewportDiv); - this.viewportDiv.classList.add("title-bar"); - } - - public measure(size: ui.ISize): ui.ISize { - return { width: size.width, height: 40 }; - } - - public setTitle(title: string) { - this.viewportDiv.innerHTML = `${title} `; - } - - public setBackgroundColor(title: string) { - const rgb = this.hexToRGB(this.intToHex(this.hashCode(title))); - const gradient = `linear-gradient(to right, rgba(${rgb[0]},${rgb[1]},${rgb[2]},0), - rgba(${rgb[0]},${rgb[1]},${rgb[2]},1))`; - this.element.style.background = gradient; - } - - public setVisibility(visible: boolean) { - this.element.style.visibility = visible ? "visible" : "hidden"; - } - - protected resizeCore(bounds: ui.Rectangle) { - const viewportRect = bounds.inner(0.92); - ui.Rectangle.conformElementToRect(this.viewportDiv, viewportRect); - } - - // Implementation of java String#hashCode - private hashCode(str: string): number { - let hash = 0; - for (let i = 0; i < str.length; i++) { - hash = str.charCodeAt(i) + ((hash << 5) - hash); - } - return hash; - } - - // Integer to RGB color converter. - private intToHex(code: number): string { - const c = (code & 0x00ffffff).toString(16).toUpperCase(); - return "00000".substring(0, 6 - c.length) + c; - } - - private hexToRGB(hex: string): number[] { - let _hex = hex; - if (_hex.length === 3) { - _hex = _hex[0] + _hex[0] + _hex[1] + _hex[1] + _hex[2] + _hex[2]; - } - const num = parseInt(_hex, 16); - return [num >> 16, (num >> 8) & 255, num & 255]; - } -} - -/* eslint-enable no-bitwise */ diff --git a/examples/data-objects/shared-text/src/client-ui-lib/index.ts b/examples/data-objects/shared-text/src/client-ui-lib/index.ts deleted file mode 100644 index f6dccb265a8f..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -// Packaging and re-exporting of fluid UI framework - -import * as controls from "./controls"; -import * as ui from "./ui"; - -export { ui }; - -export { controls }; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/text/characterCodes.ts b/examples/data-objects/shared-text/src/client-ui-lib/text/characterCodes.ts deleted file mode 100644 index b84e3ed2df47..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/text/characterCodes.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export enum CharacterCodes { - _ = 95, - $ = 36, - S4 = 52, - ampersand = 38, // & - asterisk = 42, // * - at = 64, // @ - backslash = 92, // \ - bar = 124, // | - caret = 94, // ^ - closeBrace = 125, // } - closeBracket = 93, // ] - closeParen = 41, // ) - colon = 58, // : - comma = 44, // , - dot = 46, // . - doubleQuote = 34, // " - equals = 61, // = - exclamation = 33, // ! - hash = 35, // # - greaterThan = 62, // > - lessThan = 60, // < - minus = 45, // - - openBrace = 123, // { - openBracket = 91, // [ - openParen = 40, // ( - percent = 37, // % - plus = 43, // + - question = 63, // ? - semicolon = 59, // ; - singleQuote = 39, // ' - slash = 47, // / - tilde = 126, // ~ - linefeed = 10, // \n - cr = 13, // \r - _0 = 48, - _9 = 57, - a = 97, - b = 98, - g = 103, - l = 108, - z = 122, - - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - space = 0x0020, // " " -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/text/index.ts b/examples/data-objects/shared-text/src/client-ui-lib/text/index.ts deleted file mode 100644 index 2538568a8dcc..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/text/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { CharacterCodes } from "./characterCodes"; -import * as Paragraph from "./paragraph"; -import * as Table from "./table"; - -export { Table }; -export { Paragraph }; -export { CharacterCodes }; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/text/paragraph.ts b/examples/data-objects/shared-text/src/client-ui-lib/text/paragraph.ts deleted file mode 100644 index 4ce14c3bdbf8..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/text/paragraph.ts +++ /dev/null @@ -1,597 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable import/no-deprecated */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ - -import * as MergeTree from "@fluidframework/merge-tree"; -import { refHasRangeLabel, refHasTileLabel } from "@fluidframework/merge-tree"; -import * as Sequence from "@fluidframework/sequence"; -import { CharacterCodes } from "./characterCodes"; - -type SharedString = Sequence.SharedString; - -export interface IBreakInfo { - posInPG: number; - startItemIndex: number; -} - -export interface IParagraphInfo { - breaks: IBreakInfo[]; - isUniformWidth?: boolean; - uniformLineWidth?: number; -} - -export interface IParagraphItemInfo { - minWidth: number; - maxHeight?: number; - items: ParagraphItem[]; -} - -export interface IListInfo { - itemCounts: number[]; -} - -export interface IParagraphMarker extends MergeTree.Marker { - cache?: IParagraphInfo; - itemCache?: IParagraphItemInfo; - listHeadCache?: IListHeadInfo; - listCache?: IListInfo; -} - -export enum ParagraphItemType { - Block, - Glue, - Marker, - Penalty, -} - -export interface IParagraphItem { - type: ParagraphItemType; - width: number; - segment: MergeTree.ISegment; - pos?: number; - // Present if not default - height?: number; - fontstr?: string; -} - -export interface IPGBlock extends IParagraphItem { - type: ParagraphItemType.Block; - - // TODO: Only the string's length property appears to be used to determine how many - // positions the block occupies. Consider making this 'positionLength' or similar. - text: string; -} - -export interface IPGMarker extends IParagraphItem { - type: ParagraphItemType.Marker; - segment: MergeTree.Marker; -} - -function makeIPGBlock(width: number, text: string, textSegment: MergeTree.TextSegment) { - return { type: ParagraphItemType.Block, width, text, segment: textSegment }; -} - -function makeIPGMarker(marker: MergeTree.Marker) { - return { type: ParagraphItemType.Marker, width: 0, segment: marker }; -} - -function makeGlue( - width: number, - text: string, - textSegment: MergeTree.TextSegment, - stretch: number, - shrink: number, -) { - return { - type: ParagraphItemType.Glue, - width, - text, - segment: textSegment, - stretch, - shrink, - }; -} - -export interface IPGGlue extends IParagraphItem { - type: ParagraphItemType.Glue; - text: string; - stretch: number; - shrink: number; -} - -export interface IPGPenalty extends IParagraphItem { - type: ParagraphItemType.Penalty; - cost: number; -} - -export type ParagraphItem = IPGBlock | IPGGlue | IPGPenalty | IPGMarker; - -// For now assume uniform line widths -export function breakPGIntoLinesFF(items: ParagraphItem[], lineWidth: number) { - const breaks = [{ posInPG: 0, startItemIndex: 0 }]; - let posInPG = 0; - let committedItemsWidth = 0; - let blockRunWidth = 0; - let blockRunPos = -1; - let prevIsGlue = true; - for (let i = 0, len = items.length; i < len; i++) { - const item = items[i]; - if (item.type === ParagraphItemType.Block) { - item.pos = posInPG; - if (prevIsGlue) { - blockRunPos = posInPG; - blockRunWidth = 0; - } - if (committedItemsWidth + item.width > lineWidth) { - breaks.push({ posInPG: blockRunPos, startItemIndex: i }); - committedItemsWidth = blockRunWidth; - } - posInPG += item.text.length; - if (committedItemsWidth > lineWidth) { - breaks.push({ posInPG, startItemIndex: i }); - committedItemsWidth = 0; - blockRunWidth = 0; - blockRunPos = posInPG; - } else { - blockRunWidth += item.width; - } - prevIsGlue = false; - } else if (item.type === ParagraphItemType.Glue) { - posInPG++; - prevIsGlue = true; - } - committedItemsWidth += item.width; - } - return breaks; -} - -export const enum ParagraphLexerState { - AccumBlockChars, - AccumSpaces, -} - -export interface ParagraphTokenActions { - textToken( - text: string, - type: ParagraphItemType, - leadSegment: MergeTree.TextSegment, - context?: TContext, - ): void; - markerToken(marker: MergeTree.Marker, context?: TContext): void; -} - -export class ParagraphLexer { - public state = ParagraphLexerState.AccumBlockChars; - private spaceCount = 0; - private textBuf = ""; - private leadSegment: MergeTree.TextSegment | undefined; - - constructor( - public tokenActions: ParagraphTokenActions, - public actionContext?: TContext, - ) {} - - public reset() { - this.state = ParagraphLexerState.AccumBlockChars; - this.spaceCount = 0; - this.textBuf = ""; - this.leadSegment = undefined; - } - - public mark(marker: MergeTree.Marker) { - this.emit(); - this.emitMarker(marker); - } - - public lex(textSegment: MergeTree.TextSegment) { - if ( - this.leadSegment && - !MergeTree.matchProperties(this.leadSegment.properties, textSegment.properties) - ) { - this.emit(); - this.leadSegment = textSegment; - } else if (!this.leadSegment) { - this.leadSegment = textSegment; - } - const segText = textSegment.text; - for (let i = 0, len = segText.length; i < len; i++) { - const c = segText.charAt(i); - if (c === " ") { - if (this.state === ParagraphLexerState.AccumBlockChars) { - this.emitBlock(); - } - this.state = ParagraphLexerState.AccumSpaces; - this.spaceCount++; - } else { - if (this.state === ParagraphLexerState.AccumSpaces) { - this.emitGlue(); - } - this.state = ParagraphLexerState.AccumBlockChars; - this.textBuf += c; - } - } - this.emit(); - } - - private emit() { - if (this.state === ParagraphLexerState.AccumBlockChars) { - this.emitBlock(); - } else { - this.emitGlue(); - } - } - - private emitGlue() { - if (this.spaceCount > 0) { - this.tokenActions.textToken( - MergeTree.internedSpaces(this.spaceCount), - ParagraphItemType.Glue, - this.leadSegment!, - this.actionContext, - ); - this.spaceCount = 0; - } - } - - private emitBlock() { - if (this.textBuf.length > 0) { - this.tokenActions.textToken( - this.textBuf, - ParagraphItemType.Block, - this.leadSegment!, - this.actionContext, - ); - this.textBuf = ""; - } - } - - private emitMarker(marker: MergeTree.Marker) { - this.tokenActions.markerToken(marker, this.actionContext); - } -} - -export function clearContentCaches(pgMarker: IParagraphMarker) { - pgMarker.cache = undefined; - pgMarker.itemCache = undefined; -} - -export function getIndentPct(pgMarker: IParagraphMarker) { - if (pgMarker.properties && pgMarker.properties.indentLevel !== undefined) { - return pgMarker.properties.indentLevel * 0.05; - } else if (pgMarker.properties && pgMarker.properties.blockquote) { - return 0.1; - } else { - return 0.0; - } -} - -export function getIndentSymbol(pgMarker: IParagraphMarker) { - let indentLevel = pgMarker.properties!.indentLevel; - indentLevel = indentLevel % pgMarker.listHeadCache!.series!.length; - let series = pgMarker.listHeadCache!.series![indentLevel]; - let seriesSource = listSeries; - if (pgMarker.properties!.listKind === 1) { - seriesSource = symbolSeries; - } - series = series % seriesSource.length; - return seriesSource[series](pgMarker.listCache!.itemCounts[indentLevel]); -} - -export interface IListHeadInfo { - series?: number[]; - tile: IParagraphMarker; -} - -export interface ITilePos { - tile: MergeTree.Marker; - pos: number; -} - -function getPrecedingTile( - sharedString: SharedString, - tile: MergeTree.Marker, - tilePos: number, - label: string, - filter: (candidate: MergeTree.Marker) => boolean, - precedingTileCache?: ITilePos[], -) { - let _tilePos = tilePos; - if (precedingTileCache) { - for (let i = precedingTileCache.length - 1; i >= 0; i--) { - const candidate = precedingTileCache[i]; - if (filter(candidate.tile)) { - return candidate; - } - } - } - while (_tilePos > 0) { - _tilePos = _tilePos - 1; - const prevTileInfo = sharedString.findTile(_tilePos, label); - if (prevTileInfo && filter(prevTileInfo.tile)) { - return prevTileInfo; - } - } -} - -export const isListTile = (tile: IParagraphMarker) => refHasTileLabel(tile, "list"); - -export interface ISymbol { - font?: string; - text: string; -} - -const numberSuffix = (itemIndex: number, suffix: string): ISymbol => ({ - text: itemIndex.toString() + suffix, -}); - -// TODO: more than 26 -function alphaSuffix(itemIndex: number, suffix: string, little = false) { - let code = itemIndex - 1 + CharacterCodes.A; - if (little) { - code += 32; - } - const prefix = String.fromCharCode(code); - return { text: prefix + suffix }; -} - -// TODO: more than 10 -const romanNumbers = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]; - -function roman(itemIndex: number, little = false) { - let text = `${romanNumbers[itemIndex - 1]}.`; - if (little) { - text = text.toLowerCase(); - } - return { text }; -} - -// Let wingdingLetters = ["l", "m", "n", "R", "S", "T", "s","w"]; -const unicodeBullets = [ - "\u2022", - "\u25E6", - "\u25AA", - "\u2731", - "\u272F", - "\u2729", - "\u273F", - "\u2745", - "\u2739", - "\u2720", - "\u2722", -]; - -function itemSymbols(itemIndex: number, indentLevel: number) { - // Let wingdingLetter = wingdingLetters[indentLevel - 1]; - const wingdingLetter = unicodeBullets[indentLevel - 1]; - // Return { text: wingdingLetter, font: "12px Wingdings" }; - return { text: wingdingLetter }; -} - -const listSeries = [ - (itemIndex) => numberSuffix(itemIndex, "."), - (itemIndex) => numberSuffix(itemIndex, ")"), - (itemIndex) => alphaSuffix(itemIndex, ".", true), - (itemIndex) => alphaSuffix(itemIndex, ")", true), - (itemIndex) => alphaSuffix(itemIndex, "."), - (itemIndex) => alphaSuffix(itemIndex, ")"), - (itemIndex) => roman(itemIndex, true), - (itemIndex) => roman(itemIndex), -]; - -const symbolSeries = [ - (itemIndex) => itemSymbols(itemIndex, 1), - (itemIndex) => itemSymbols(itemIndex, 2), - (itemIndex) => itemSymbols(itemIndex, 3), - (itemIndex) => itemSymbols(itemIndex, 4), - (itemIndex) => itemSymbols(itemIndex, 5), - (itemIndex) => itemSymbols(itemIndex, 6), - (itemIndex) => itemSymbols(itemIndex, 7), - (itemIndex) => itemSymbols(itemIndex, 8), - (itemIndex) => itemSymbols(itemIndex, 9), - (itemIndex) => itemSymbols(itemIndex, 10), - (itemIndex) => itemSymbols(itemIndex, 11), -]; - -function convertToListHead(tile: IParagraphMarker) { - tile.listHeadCache = { - series: tile.properties!.series, - tile, - }; - tile.listCache = { itemCounts: [0, 1] }; -} - -/** - * Maximum number of characters before a preceding list paragraph deemed irrelevant - */ -const maxListDistance = 400; - -export function getListCacheInfo( - sharedString: SharedString, - tile: IParagraphMarker, - tilePos: number, - precedingTileCache?: ITilePos[], -) { - if (isListTile(tile)) { - if (tile.listCache === undefined) { - if (tile.properties!.series) { - convertToListHead(tile); - } else { - const listKind = tile.properties!.listKind; - const precedingTilePos = getPrecedingTile( - sharedString, - tile, - tilePos, - "list", - (t) => isListTile(t) && t.properties!.listKind === listKind, - precedingTileCache, - ); - if (precedingTilePos && tilePos - precedingTilePos.pos < maxListDistance) { - getListCacheInfo( - sharedString, - precedingTilePos.tile, - precedingTilePos.pos, - precedingTileCache, - ); - const precedingTile = precedingTilePos.tile; - tile.listHeadCache = precedingTile.listHeadCache; - const indentLevel = tile.properties!.indentLevel; - const precedingItemCount = precedingTile.listCache!.itemCounts[indentLevel]; - const itemCounts = precedingTile.listCache!.itemCounts.slice(); - itemCounts[indentLevel] = - indentLevel < itemCounts.length ? precedingItemCount + 1 : 1; - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - for (let i = indentLevel + 1; i < itemCounts.length; i++) { - itemCounts[i] = 0; - } - tile.listCache = { itemCounts }; - } else { - // Doesn't race because re-render is deferred - const series: number[] = - tile.properties!.listKind === 0 - ? [0, 0, 2, 6, 3, 7, 2, 6, 3, 7] - : [0, 0, 1, 2, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6]; - sharedString.annotateRange(tilePos, tilePos + 1, { series }); - convertToListHead(tile); - } - } - } - } -} - -export function getContentPct(pgMarker: IParagraphMarker) { - if (pgMarker.properties && pgMarker.properties.contentWidth) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return pgMarker.properties.contentWidth; - } else if (pgMarker.properties && pgMarker.properties.blockquote) { - return 0.8; - } else { - return 1.0; - } -} - -export interface IHeightWidth { - h: number; - w: number; -} - -export interface IFontInfo { - getTextWidth(text: string, fontstr: string); - getLineHeight(fontstr: string, lineHeight?: string): number; - getFont(pg: IParagraphMarker): string; -} - -export interface IItemsContext { - fontInfo: IFontInfo; - curPGMarker: IParagraphMarker; - nextPGPos: number; - itemInfo: IParagraphItemInfo; - paragraphLexer: ParagraphLexer; -} - -export function markerToItems(marker: MergeTree.Marker, itemsContext: IItemsContext) { - const items = itemsContext.itemInfo.items; - - // Assume this is a paragraph marker - items.push(makeIPGMarker(marker)); -} - -export function textTokenToItems( - text: string, - type: ParagraphItemType, - leadSegment: MergeTree.TextSegment, - itemsContext: IItemsContext, -) { - const fontInfo = itemsContext.fontInfo; - const pgFontstr = fontInfo.getFont(itemsContext.curPGMarker); - let lfontstr = pgFontstr; - const pgLineHeight = fontInfo.getLineHeight(lfontstr); - itemsContext.itemInfo.maxHeight = pgLineHeight; - let divHeight = pgLineHeight; - if (leadSegment.properties) { - let fontFamily = "Times"; - if (leadSegment.properties.fontFamily) { - fontFamily = leadSegment.properties.fontFamily; - } - const fontSize = leadSegment.properties.fontSize; - if (fontSize !== undefined) { - lfontstr = `${fontSize} ${fontFamily}`; - divHeight = +fontSize; - } - // This is not complete because can be % or normal etc. - const lineHeight = leadSegment.properties.lineHeight; - if (lineHeight !== undefined) { - divHeight = Math.floor(+lineHeight * divHeight); - } - const fontWeight = leadSegment.properties.fontWeight; - if (fontWeight) { - lfontstr = `${fontWeight} ${lfontstr}`; - } - const fontStyle = leadSegment.properties.fontStyle; - if (fontStyle) { - lfontstr = `${fontStyle} ${lfontstr}`; - } - } - - const textWidth = fontInfo.getTextWidth(text, lfontstr); - if (textWidth > itemsContext.itemInfo.minWidth) { - itemsContext.itemInfo.minWidth = textWidth; - } - if (type === ParagraphItemType.Block) { - const block = makeIPGBlock(textWidth, text, leadSegment); - if (lfontstr !== pgFontstr) { - block.fontstr = lfontstr; - } - if (divHeight !== pgLineHeight) { - block.height = divHeight; - if (divHeight > itemsContext.itemInfo.maxHeight) { - itemsContext.itemInfo.maxHeight = divHeight; - } - } - itemsContext.itemInfo.items.push(block); - } else { - const wordSpacing = fontInfo.getTextWidth(" ", lfontstr); - itemsContext.itemInfo.items.push( - makeGlue(textWidth, text, leadSegment, wordSpacing / 2, wordSpacing / 3), - ); - } -} - -export const isEndBox = (marker: MergeTree.Marker) => - // eslint-disable-next-line no-bitwise - marker.refType & MergeTree.ReferenceType.NestEnd && refHasRangeLabel(marker, "box"); - -export const referenceProperty = "ref"; - -export const isReference = (marker: MergeTree.Marker) => marker.hasProperty(referenceProperty); - -export function segmentToItems( - segment: MergeTree.ISegment, - segpos: number, - refSeq: number, - clientId: number, - start: number, - end: number, - context: IItemsContext, -) { - if (MergeTree.TextSegment.is(segment)) { - context.paragraphLexer.lex(segment); - } else if (MergeTree.Marker.is(segment)) { - if (isReference(segment)) { - context.paragraphLexer.mark(segment); - } else if (refHasTileLabel(segment, "pg") || isEndBox(segment)) { - context.nextPGPos = segpos; - return false; - } - } - return true; -} - -/* eslint-enable @typescript-eslint/no-non-null-assertion */ -/* eslint-enable @typescript-eslint/consistent-type-assertions */ -/* eslint-enable @typescript-eslint/strict-boolean-expressions */ diff --git a/examples/data-objects/shared-text/src/client-ui-lib/text/table.ts b/examples/data-objects/shared-text/src/client-ui-lib/text/table.ts deleted file mode 100644 index 497d124f966f..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/text/table.ts +++ /dev/null @@ -1,989 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable import/no-deprecated */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -/* eslint-disable @typescript-eslint/strict-boolean-expressions */ - -import { assert } from "@fluidframework/core-utils"; -import * as MergeTree from "@fluidframework/merge-tree"; -import { - refGetRangeLabels, - refHasRangeLabel, - refHasRangeLabels, - refHasTileLabel, -} from "@fluidframework/merge-tree"; -import * as Sequence from "@fluidframework/sequence"; -import * as Paragraph from "./paragraph"; - -type SharedString = Sequence.SharedString; - -export interface ITableMarker extends MergeTree.Marker { - table?: Table; -} - -export interface IRowMarker extends MergeTree.Marker { - row?: Row; -} - -export interface IColumnMarker extends MergeTree.Marker { - column?: Column; - columnId?: string; - indexInTable?: number; -} - -export interface ICellMarker extends MergeTree.Marker { - cell?: Cell; -} - -let tableIdSuffix = 0; -let cellIdSuffix = 0; -let rowIdSuffix = 0; -let columnIdSuffix = 0; - -const getPosition = (sharedString: SharedString, segment: MergeTree.ISegment) => - sharedString.getPosition(segment); - -function createRelativeMarkerOp( - relativePos1: MergeTree.IRelativePosition, - id: string, - refType: MergeTree.ReferenceType, - rangeLabels: string[], - tileLabels?: string[], - props?: MergeTree.PropertySet, -) { - let _props = props; - if (!_props) { - _props = {} as MergeTree.MapLike; - } - - if (id.length > 0) { - _props[MergeTree.reservedMarkerIdKey] = id; - } - - if (rangeLabels.length > 0) { - _props[MergeTree.reservedRangeLabelsKey] = rangeLabels; - } - if (tileLabels) { - _props[MergeTree.reservedTileLabelsKey] = tileLabels; - } - return { - seg: { marker: { refType }, props: _props }, - relativePos1, - type: MergeTree.MergeTreeDeltaType.INSERT, - } as MergeTree.IMergeTreeInsertMsg; -} - -export function createMarkerOp( - pos1: number, - id: string, - refType: MergeTree.ReferenceType, - rangeLabels: string[], - tileLabels?: string[], - props?: MergeTree.PropertySet, -) { - let _props = props; - if (!_props) { - _props = {} as MergeTree.MapLike; - } - if (id.length > 0) { - _props[MergeTree.reservedMarkerIdKey] = id; - } - if (rangeLabels.length > 0) { - _props[MergeTree.reservedRangeLabelsKey] = rangeLabels; - } - if (tileLabels) { - _props[MergeTree.reservedTileLabelsKey] = tileLabels; - } - return { - seg: { marker: { refType }, props: _props }, - pos1, - type: MergeTree.MergeTreeDeltaType.INSERT, - } as MergeTree.IMergeTreeInsertMsg; -} - -const endPrefix = "end-"; - -export const idFromEndId = (endId: string) => endId.substring(endPrefix.length); - -function createCellBegin( - opList: MergeTree.IMergeTreeOp[], - cellEndId: string, - cellId: string, - extraProperties?: MergeTree.PropertySet, -) { - const cellEndRelPos = { - before: true, - id: cellEndId, - }; - let startExtraProperties: Record | undefined; - let pgExtraProperties: Record | undefined; - if (extraProperties) { - startExtraProperties = MergeTree.extend(MergeTree.createMap(), extraProperties); - pgExtraProperties = MergeTree.extend(MergeTree.createMap(), extraProperties); - } - opList.push( - createRelativeMarkerOp( - cellEndRelPos, - cellId, - MergeTree.ReferenceType.NestBegin, - ["cell"], - undefined, - startExtraProperties, - ), - ); - const pgOp = createRelativeMarkerOp( - cellEndRelPos, - `${cellId}C`, - MergeTree.ReferenceType.Tile, - [], - ["pg"], - pgExtraProperties, - ); - opList.push(pgOp); -} - -function createCellRelativeWithId( - opList: MergeTree.IMergeTreeOp[], - cellId: string, - relpos: MergeTree.IRelativePosition, - extraProperties?: MergeTree.PropertySet, -) { - const cellEndId = endPrefix + cellId; - let endExtraProperties: Record | undefined; - if (extraProperties) { - endExtraProperties = MergeTree.extend(MergeTree.createMap(), extraProperties); - } - opList.push( - createRelativeMarkerOp( - relpos, - cellEndId, - MergeTree.ReferenceType.NestEnd, - ["cell"], - undefined, - endExtraProperties, - ), - ); - createCellBegin(opList, cellEndId, cellId, extraProperties); -} - -function createCellRelative( - opList: MergeTree.IMergeTreeOp[], - idBase: string, - relpos: MergeTree.IRelativePosition, - extraProperties?: MergeTree.PropertySet, -) { - const cellId = `${idBase}cell${cellIdSuffix++}`; - createCellRelativeWithId(opList, cellId, relpos, extraProperties); -} - -function createEmptyRowAfter( - opList: MergeTree.IMergeTreeOp[], - sharedString: SharedString, - prevRow: Row, - rowId: string, -) { - const endRowPos = { - id: prevRow.endRowMarker.getId(), - }; - opList.push( - createRelativeMarkerOp(endRowPos, endPrefix + rowId, MergeTree.ReferenceType.NestEnd, [ - "row", - ]), - ); - opList.push( - createRelativeMarkerOp(endRowPos, rowId, MergeTree.ReferenceType.NestBegin, ["row"]), - ); -} - -function createRowCellOp( - opList: MergeTree.IMergeTreeOp[], - sharedString: SharedString, - idBase: string, - endRowId: string, - columnId?: string, -) { - let props: MergeTree.PropertySet | undefined; - if (columnId) { - props = { columnId }; - } - createCellRelative(opList, idBase, { id: endRowId, before: true }, props); -} - -function createColumnCellOp( - idBase: string, - opList: MergeTree.IMergeTreeOp[], - row: Row, - prevCell: Cell, - columnId: string, - extraProperties?: MergeTree.PropertySet, -) { - const id = prevCell.endMarker.getId(); - createCellRelative(opList, idBase, { id }, { columnId }); -} - -function insertColumnCellForRow( - idBase: string, - opList: MergeTree.IMergeTreeOp[], - row: Row, - prevColId: string, - colId: string, -) { - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < row.cells.length; i++) { - const prevCell = row.cells[i]; - if (prevCell.columnId === prevColId) { - createColumnCellOp(idBase, opList, row, prevCell, colId); - } - } -} - -const traceOps = true; - -// TODO: non-grid case -export function insertColumn( - sharedString: SharedString, - idBase: string, - prevCell: Cell, - row: Row, - table: Table, -) { - const prevColumnId = prevCell.columnId; - const columnId = `${idBase}Col${columnIdSuffix++}`; - if (traceOps) { - console.log(`insert col prev ${prevCell.marker.toString()} id: ${columnId}`); - } - const opList: MergeTree.IMergeTreeOp[] = []; - const insertColMarkerOp = { - seg: { - marker: { - refType: MergeTree.ReferenceType.Simple, - }, - props: { columnId, [MergeTree.reservedMarkerIdKey]: columnId }, - }, - relativePos1: { id: prevColumnId }, - type: MergeTree.MergeTreeDeltaType.INSERT, - } as MergeTree.IMergeTreeInsertMsg; - opList.push(insertColMarkerOp); - for (const currRow of table.rows) { - insertColumnCellForRow(idBase, opList, currRow, prevColumnId!, columnId); - } - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); - - // Flush cache - table.tableMarker.table = undefined; -} - -export function insertRowCellForColumn( - sharedString: SharedString, - opList: MergeTree.IMergeTreeOp[], - prevCell: Cell, - idBase: string, - endRowId: string, -) { - createRowCellOp(opList, sharedString, idBase, endRowId, prevCell.columnId); -} - -// TODO: non-grid -// TODO: GC using consensus remove -export function deleteColumn( - sharedString: SharedString, - clientId: string, - cell: Cell, - row: Row, - table: Table, -) { - if (traceOps) { - console.log(`delete column from cell ${cell.marker.toString()}`); - } - const columnId = cell.columnId; - for (const currRow of table.rows) { - for (const currCell of currRow.cells) { - if (currCell.columnId === columnId) { - sharedString.annotateMarkerNotifyConsensus( - currCell.marker, - { moribund: clientId }, - (m) => { - sharedString.removeRange( - sharedString.getPosition(currCell.marker), - sharedString.getPosition(currCell.endMarker), - ); - }, - ); - } - } - } - const opList: MergeTree.IMergeTreeOp[] = []; - const removeColMarkerOp = { - relativePos1: { id: columnId, before: true }, - relativePos2: { id: columnId }, - type: MergeTree.MergeTreeDeltaType.REMOVE, - } as MergeTree.IMergeTreeRemoveMsg; - opList.push(removeColMarkerOp); - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); - table.tableMarker.table = undefined; -} - -// TODO: GC using consensus remove -export function deleteCellShiftLeft(sharedString: SharedString, cell: Cell, table: Table) { - const cellPos = getPosition(sharedString, cell.marker); - const annotOp = { - pos1: cellPos, - pos2: cellPos + cell.marker.cachedLength, - props: { moribund: true }, - type: MergeTree.MergeTreeDeltaType.ANNOTATE, - } as MergeTree.IMergeTreeAnnotateMsg; - const opList = [annotOp]; - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); - table.tableMarker.table = undefined; -} - -// TODO: GC using consensus remove -export function deleteRow(sharedString: SharedString, row: Row, table: Table) { - if (traceOps) { - console.log(`delete row ${row.rowMarker.getId()}`); - } - const rowPos = getPosition(sharedString, row.rowMarker); - const annotOp = { - pos1: rowPos, - pos2: rowPos + row.rowMarker.cachedLength, - props: { moribund: true }, - type: MergeTree.MergeTreeDeltaType.ANNOTATE, - } as MergeTree.IMergeTreeAnnotateMsg; - const opList = [annotOp]; - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); - table.tableMarker.table = undefined; -} - -export function insertRow(sharedString: SharedString, idBase: string, prevRow: Row, table: Table) { - const rowId = `${idBase}row${rowIdSuffix++}`; - if (traceOps) { - console.log(`insert row id: ${rowId} prev: ${prevRow.rowMarker.getId()}`); - } - const opList: MergeTree.IMergeTreeOp[] = []; - createEmptyRowAfter(opList, sharedString, prevRow, rowId); - const endRowId = endPrefix + rowId; - for (let i = 0, len = prevRow.cells.length; i < len; i++) { - insertRowCellForColumn(sharedString, opList, prevRow.cells[i], idBase, endRowId); - } - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); - - // Flush cache - table.tableMarker.table = undefined; -} - -// Table Column* (Row (Cell EndCell)* EndRow)* EndTable -export function createTable( - pos: number, - sharedString: SharedString, - idBase: string, - nrows = 3, - ncells = 3, -) { - let pgAtStart = true; - if (pos > 0) { - const segoff = sharedString.getContainingSegment(pos - 1); - assert(segoff.segment !== undefined, "segment cannot be undefined here"); - if (MergeTree.Marker.is(segoff.segment)) { - if (refHasTileLabel(segoff.segment, "pg")) { - pgAtStart = false; - } - } - } - const tableId = `T${tableIdSuffix++}`; - const opList: MergeTree.IMergeTreeInsertMsg[] = []; - const endTableId = endPrefix + tableId; - opList.push( - createMarkerOp( - pos, - endTableId, - // eslint-disable-next-line no-bitwise - MergeTree.ReferenceType.NestEnd | MergeTree.ReferenceType.Tile, - ["table"], - ["pg"], - ), - ); - const endTablePos = { - before: true, - id: endTableId, - } as MergeTree.IRelativePosition; - if (pgAtStart) { - // TODO: copy pg properties from pg marker after pos - const pgOp = createRelativeMarkerOp( - endTablePos, - "", - MergeTree.ReferenceType.Tile, - [], - ["pg"], - ); - opList.push(pgOp); - } - opList.push( - createRelativeMarkerOp( - endTablePos, - tableId, - MergeTree.ReferenceType.NestBegin, - ["table"], - [], - { rectTable: true }, - ), - ); - const columnIds = []; - for (let row = 0; row < nrows; row++) { - const rowId = `${idBase}row${rowIdSuffix++}`; - opList.push( - createRelativeMarkerOp(endTablePos, rowId, MergeTree.ReferenceType.NestBegin, ["row"]), - ); - for (let cell = 0; cell < ncells; cell++) { - if (!columnIds[cell]) { - columnIds[cell] = `${idBase}col${columnIdSuffix++}`; - } - const props = { columnId: columnIds[cell] }; - createCellRelative(opList, idBase, endTablePos, props); - } - opList.push( - createRelativeMarkerOp( - endTablePos, - endPrefix + rowId, - MergeTree.ReferenceType.NestEnd, - ["row"], - ), - ); - } - for (let i = columnIds.length - 1; i >= 0; i--) { - const columnId = columnIds[i]; - const insertColMarkerOp = { - seg: { - marker: { - refType: MergeTree.ReferenceType.Simple, - }, - props: { columnId, [MergeTree.reservedMarkerIdKey]: columnId }, - }, - relativePos1: { id: tableId }, - type: MergeTree.MergeTreeDeltaType.INSERT, - } as MergeTree.IMergeTreeInsertMsg; - opList.push(insertColMarkerOp); - } - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - } as MergeTree.IMergeTreeGroupMsg; - sharedString.groupOperation(groupOp); -} - -export class Table { - public width: number | undefined; - public renderedHeight: number | undefined; - public deferredHeight: number | undefined; - public minContentWidth = 0; - public indentPct = 0.0; - public contentPct = 1.0; - public rows: Row[] = []; - public logicalColumns: Column[] = []; - public gridColumns: IColumnMarker[] = []; - public idToColumn = new Map(); - constructor( - public tableMarker: ITableMarker, - public endTableMarker: ITableMarker, - ) {} - - public addGridColumn(columnMarker: IColumnMarker) { - columnMarker.columnId = columnMarker.getId(); - this.idToColumn.set(columnMarker.columnId!, columnMarker); - columnMarker.indexInTable = this.gridColumns.length; - this.gridColumns.push(columnMarker); - } - - public nextcell(cell: Cell) { - let retNext = false; - for (let rowIndex = 0, rowCount = this.rows.length; rowIndex < rowCount; rowIndex++) { - const row = this.rows[rowIndex]; - for ( - let cellIndex = 0, cellCount = row.cells.length; - cellIndex < cellCount; - cellIndex++ - ) { - const rowcell = row.cells[cellIndex]; - if (retNext && !cellIsMoribund(rowcell.marker)) { - return rowcell; - } - if (rowcell === cell) { - retNext = true; - } - } - } - } - - public prevcell(cell: Cell) { - let retPrev = false; - for (let rowIndex = this.rows.length - 1; rowIndex >= 0; rowIndex--) { - const row = this.rows[rowIndex]; - for (let cellIndex = row.cells.length - 1; cellIndex >= 0; cellIndex--) { - const rowcell = row.cells[cellIndex]; - if (retPrev && !cellIsMoribund(rowcell.marker)) { - return rowcell; - } - if (rowcell === cell) { - retPrev = true; - } - } - } - } - - public findPrecedingRow(startRow: Row) { - let prevRow: Row | undefined; - for (let rowIndex = 0, rowCount = this.rows.length; rowIndex < rowCount; rowIndex++) { - const row = this.rows[rowIndex]; - if (row === startRow) { - return prevRow; - } - if (!rowIsMoribund(row.rowMarker)) { - prevRow = row; - } - } - } - - public findNextRow(startRow: Row) { - let nextRow: Row | undefined; - for (let rowIndex = this.rows.length - 1; rowIndex >= 0; rowIndex--) { - const row = this.rows[rowIndex]; - if (row === startRow) { - return nextRow; - } - if (!rowIsMoribund(row.rowMarker)) { - nextRow = row; - } - } - } - - public updateWidth(w: number) { - this.width = w; - let liveColumnCount = 0; - for (let i = 0, len = this.logicalColumns.length; i < len; i++) { - const col = this.logicalColumns[i]; - if (!col.moribund) { - liveColumnCount++; - } - } - let proportionalWidthPerColumn = Math.floor(this.width / liveColumnCount); - // Assume remaining width positive for now - // assume uniform number of columns in rows for now (later update each row separately) - let abscondedWidth = 0; - let totalWidth = 0; - for (let i = 0, len = this.logicalColumns.length; i < len; i++) { - const col = this.logicalColumns[i]; - // TODO: borders - if (!col.moribund) { - if (col.minContentWidth > proportionalWidthPerColumn) { - col.width = col.minContentWidth; - abscondedWidth += col.width; - proportionalWidthPerColumn = Math.floor( - (this.width - abscondedWidth) / (len - i), - ); - } else { - col.width = proportionalWidthPerColumn; - } - totalWidth += col.width; - if (i === len - 1) { - if (totalWidth < this.width) { - col.width += this.width - totalWidth; - } - } - for (const cell of col.cells) { - if (cell) { - cell.specWidth = col.width; - } - } - } - } - } -} - -export class Column { - public minContentWidth = 0; - public width = 0; - public cells = []; - public moribund = false; - constructor(public columnIndex: number) {} -} - -export class Row { - public table: Table | undefined; - public pos: number | undefined; - public endPos: number | undefined; - public minContentWidth = 0; - public cells = []; - - constructor( - public rowMarker: IRowMarker, - public endRowMarker: IRowMarker, - ) {} - - // TODO: move to view layer - public findClosestCell(x: number) { - let bestcell: Cell | undefined; - let bestDistance = -1; - for (const cell of this.cells) { - if (cell.div) { - const bounds = cell.div.getBoundingClientRect(); - const center = bounds.left + bounds.width / 2; - const distance = Math.abs(center - x); - if (distance < bestDistance || bestDistance < 0) { - bestcell = cell; - bestDistance = distance; - } - } - } - return bestcell; - } -} - -export class Cell { - public minContentWidth = 0; - public specWidth = 0; - public renderedHeight: number | undefined; - public div: HTMLDivElement | undefined; - public columnId: string | undefined; - // TODO: update on typing in cell - public emptyCell = false; - public additionalCellMarkers: ICellMarker[] | undefined; - constructor( - public marker: ICellMarker, - public endMarker: ICellMarker, - ) {} - public addAuxMarker(marker: ICellMarker) { - if (!this.additionalCellMarkers) { - this.additionalCellMarkers = []; - } - this.additionalCellMarkers.push(marker); - } -} - -function getEndCellMarker(sharedString: SharedString, cellMarker: ICellMarker) { - const gloId = cellMarker.getId(); - if (gloId) { - return sharedString.getMarkerFromId(endPrefix + gloId); - } -} - -function parseCell( - cellStartPos: number, - sharedString: SharedString, - fontInfo?: Paragraph.IFontInfo, -) { - const markEmptyCells = false; - const cellMarkerSegOff = sharedString.getContainingSegment(cellStartPos); - const cellMarker = cellMarkerSegOff.segment; - const endCellMarker = getEndCellMarker(sharedString, cellMarker); - if (!endCellMarker) { - console.log(`ut-oh: no end for ${cellMarker.toString()}`); - return undefined; - } - const endCellPos = getPosition(sharedString, endCellMarker); - cellMarker.cell = new Cell(cellMarker, endCellMarker); - cellMarker.cell.columnId = cellMarker.properties!.columnId; - let nextPos = cellStartPos + cellMarker.cachedLength; - if (markEmptyCells && nextPos === endCellPos - 1) { - cellMarker.cell.emptyCell = true; - } else { - while (nextPos < endCellPos) { - const segoff = sharedString.getContainingSegment(nextPos); - // TODO: model error checking - const segment = segoff.segment; - if (segment && MergeTree.Marker.is(segment)) { - const marker = segoff.segment; - if (refHasRangeLabel(marker, "table")) { - const tableMarker = marker; - parseTable(tableMarker, nextPos, sharedString, fontInfo); - if (tableMarker.table!.minContentWidth > cellMarker.cell.minContentWidth) { - cellMarker.cell.minContentWidth = tableMarker.table!.minContentWidth; - } - const endTableMarker = tableMarker.table!.endTableMarker; - nextPos = sharedString.getPosition(endTableMarker); - nextPos += endTableMarker.cachedLength; - } else { - // Empty paragraph - nextPos++; - } - } else { - // Text segment - const tilePos = sharedString.findTile(nextPos, "pg", false)!; - const pgMarker = tilePos.tile; - if (!pgMarker.itemCache) { - if (fontInfo) { - const itemsContext = ({ - curPGMarker: pgMarker, - fontInfo, - itemInfo: { items: [], minWidth: 0 }, - }); - const paragraphLexer = new Paragraph.ParagraphLexer( - { - markerToken: Paragraph.markerToItems, - textToken: Paragraph.textTokenToItems, - }, - itemsContext, - ); - itemsContext.paragraphLexer = paragraphLexer; - - sharedString.walkSegments( - Paragraph.segmentToItems, - nextPos, - tilePos.pos, - itemsContext, - ); - pgMarker.itemCache = itemsContext.itemInfo; - } - } - nextPos = tilePos.pos + 1; - if (pgMarker.itemCache) { - if (pgMarker.itemCache.minWidth > cellMarker.cell.minContentWidth) { - cellMarker.cell.minContentWidth = pgMarker.itemCache.minWidth; - } - } - } - } - } - // Console.log(`parsed cell ${cellMarker.getId()}`); - return cellMarker; -} - -function parseRow( - rowStartPos: number, - sharedString: SharedString, - table: Table, - fontInfo?: Paragraph.IFontInfo, -) { - const rowMarkerSegOff = sharedString.getContainingSegment(rowStartPos); - const rowMarker = rowMarkerSegOff.segment; - const id = rowMarker.getId(); - const endId = `${endPrefix}${id}`; - const endRowMarker = sharedString.getMarkerFromId(endId); - if (!endRowMarker) { - console.log(`row parse error: ${rowStartPos}`); - return undefined; - } - const endRowPos = getPosition(sharedString, endRowMarker); - const row = new Row(rowMarker, endRowMarker); - rowMarker.row = row; - let nextPos = rowStartPos + rowMarker.cachedLength; - const rowColumns = MergeTree.createMap(); - while (nextPos < endRowPos) { - const cellMarker = parseCell(nextPos, sharedString, fontInfo); - if (!cellMarker) { - const tableMarkerPos = getPosition(sharedString, table.tableMarker); - succinctPrintTable(table.tableMarker, tableMarkerPos, sharedString); - return undefined; - } - // TODO: check for column id not in grid - if (!cellIsMoribund(cellMarker)) { - const cellColumnId = cellMarker.properties!.columnId; - rowMarker.row.minContentWidth += cellMarker.cell!.minContentWidth; - rowMarker.row.cells.push(cellMarker.cell!); - rowColumns[cellColumnId] = cellMarker.cell!; - } - const endcellPos = getPosition(sharedString, cellMarker.cell!.endMarker); - nextPos = endcellPos + cellMarker.cell!.endMarker.cachedLength; - } - return rowMarker; -} - -export function parseColumns(sharedString: SharedString, pos: number, table: Table) { - let nextPos = pos; - function addColumn(segment: MergeTree.ISegment, segpos: number) { - nextPos = segpos; - if (MergeTree.Marker.is(segment)) { - const marker = segment; - if (marker.hasProperty("columnId")) { - table.addGridColumn(marker); - return true; - } - } - return false; - } - sharedString.walkSegments(addColumn, pos); - return nextPos; -} - -export function succinctPrintTable( - tableMarker: ITableMarker, - tableMarkerPos: number, - sharedString: SharedString, -) { - const id = tableMarker.getId(); - const endId = `${endPrefix}${id}`; - const endTableMarker = sharedString.getMarkerFromId(endId); - const endTablePos = endTableMarker.cachedLength + getPosition(sharedString, endTableMarker); - let lineBuf = ""; - let lastWasCO = false; - let reqPos = true; - function printTableSegment(segment: MergeTree.ISegment, segpos: number) { - if (MergeTree.Marker.is(segment)) { - const marker = segment; - let endLine = false; - if (reqPos) { - lineBuf += `${segpos}:`; - reqPos = false; - } - if (refHasRangeLabels(marker)) { - const rangeLabel = refGetRangeLabels(marker)![0]; - if (marker.refType === MergeTree.ReferenceType.NestEnd) { - lineBuf += "E"; - if (rangeLabel === "table" || rangeLabel === "row") { - endLine = true; - } - } - /* eslint-disable default-case */ - switch (rangeLabel) { - case "table": - lineBuf += "T"; - lastWasCO = false; - break; - case "row": - if (marker.refType === MergeTree.ReferenceType.NestBegin) { - if (lastWasCO) { - lineBuf += "\n"; - lastWasCO = false; - } - } - lineBuf += "R"; - break; - case "cell": - lineBuf += "CL"; - break; - } - /* eslint-enable default-case */ - } else if (marker.refType === MergeTree.ReferenceType.Simple) { - if (marker.properties!.columnId) { - lineBuf += "CO"; - lastWasCO = true; - } - } else if (marker.refType === MergeTree.ReferenceType.Tile) { - lineBuf += "P"; - } - if (marker.hasProperty("moribund")) { - lineBuf += "_"; - } - if (endLine) { - lineBuf += " \n"; - reqPos = true; - } else { - lineBuf += " "; - } - } else { - const textSegment = segment; - lineBuf += textSegment.text; - reqPos = true; - } - return true; - } - sharedString.walkSegments(printTableSegment, tableMarkerPos, endTablePos); - console.log(lineBuf); -} - -export function insertHoleFixer( - sharedString: SharedString, - prevMarker: MergeTree.Marker, - columnId: string, - rowId: string, -) { - const extraProperties = { - columnId, - }; - const cellId = `${rowId}X${columnId}`; - const opList = []; - createCellRelativeWithId(opList, cellId, { id: prevMarker.getId() }, extraProperties); - const groupOp = { - ops: opList, - type: MergeTree.MergeTreeDeltaType.GROUP, - }; - sharedString.groupOperation(groupOp); -} - -export function parseTable( - tableMarker: ITableMarker, - tableMarkerPos: number, - sharedString: SharedString, - fontInfo?: Paragraph.IFontInfo, -) { - const id = tableMarker.getId(); - const endId = `${endPrefix}${id}`; - const endTableMarker = sharedString.getMarkerFromId(endId); - const endTablePos = getPosition(sharedString, endTableMarker); - const table = new Table(tableMarker, endTableMarker); - tableMarker.table = table; - let nextPos = tableMarkerPos + tableMarker.cachedLength; - nextPos = parseColumns(sharedString, nextPos, tableMarker.table); - let rowIndex = 0; - while (nextPos < endTablePos) { - const rowMarker = parseRow(nextPos, sharedString, table, fontInfo); - if (!rowMarker) { - console.log("PARSE ERROR!"); - succinctPrintTable(tableMarker, tableMarkerPos, sharedString); - return undefined; - } - const rowView = rowMarker.row!; - rowView.table = table; - rowView.pos = nextPos; - if (!rowIsMoribund(rowMarker)) { - for (let i = 0, len = rowView.cells.length; i < len; i++) { - const cell = rowView.cells[i]; - if (!table.logicalColumns[i]) { - table.logicalColumns[i] = new Column(i); - } - const columnView = table.logicalColumns[i]; - columnView.cells[rowIndex] = cell; - if (cell.minContentWidth > columnView.minContentWidth) { - columnView.minContentWidth = cell.minContentWidth; - } - if (cellIsMoribund(cell.marker) && cell.marker.properties!.wholeColumn) { - columnView.moribund = true; - } - } - - if (rowMarker.row!.minContentWidth > table.minContentWidth) { - table.minContentWidth = rowMarker.row!.minContentWidth; - } - table.rows[rowIndex++] = rowView; - } - const endRowPos = getPosition(sharedString, rowMarker.row!.endRowMarker); - rowView.endPos = endRowPos; - nextPos = endRowPos + rowMarker.row!.endRowMarker.cachedLength; - } - succinctPrintTable(tableMarker, tableMarkerPos, sharedString); - return table; -} - -export const rowIsMoribund = (rowMarker: IRowMarker) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - rowMarker.properties && rowMarker.properties.moribund; - -export const cellIsMoribund = (cellMarker: ICellMarker) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - cellMarker.properties && cellMarker.properties.moribund; - -/* eslint-enable @typescript-eslint/no-non-null-assertion */ -/* eslint-enable @typescript-eslint/consistent-type-assertions */ -/* eslint-enable @typescript-eslint/strict-boolean-expressions */ diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/README.md b/examples/data-objects/shared-text/src/client-ui-lib/ui/README.md deleted file mode 100644 index 82cb60cb84b8..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Example Client UI Library - -## Design Goals - -- Viewport centric UI -- Document level eventing -- Isomorphic rendering - -## Component - -The core class is the Component. This defines a node in the UI graph and base behavior for the UI. - -## Getting started - -To get started create a new BrowserContainerHost and then attach a Component to it. The framework will then -manage eventing and rendering for the component and its children. diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/browserContainerHost.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/browserContainerHost.ts deleted file mode 100644 index 21a5cd790daf..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/browserContainerHost.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { Component } from "./component"; -import { debug } from "./debug"; -import { Rectangle } from "./geometry"; -import { removeAllChildren } from "./utils"; - -// The majority of this can likely be abstracted behind interfaces - drawing inspiration from other -// UI frameworks. For now we keep it simple and have this class manage the lifetime of the UI framework. - -/** - * Hosts a UI container within the browser - */ -export class BrowserContainerHost { - private _root: Component | undefined; - private get root(): Component { - if (this._root === undefined) { - throw new Error("Root accessed before created"); - } - return this._root; - } - private parent: HTMLElement | undefined; - - public attach(root: Component, parent?: HTMLElement) { - debug("Attaching new component to browser host"); - - // Make note of the root node - if (this._root) { - throw new Error("A component has already been attached"); - } - this._root = root; - this.parent = parent; - - // Listen for resize messages and propagate them to child elements - window.addEventListener("resize", () => { - debug("resize"); - this.resize(); - }); - - // Throttle the resizes? - - // Input event handling - document.body.onkeydown = (e) => { - this.root.emit("keydown", e); - }; - - document.body.onkeypress = (e) => { - this.root.emit("keypress", e); - }; - - if (parent) { - parent.appendChild(root.element); - } else { - removeAllChildren(document.body); - document.body.appendChild(root.element); - } - - // Trigger initial resize due to attach - this.resize(); - this.resize(); // Have to resize twice because we get a weird bounding rect the first time, not sure why - } - - private resize() { - let clientRect; - let newSize; - if (this.parent) { - clientRect = this.parent.getBoundingClientRect(); - newSize = Rectangle.fromClientRect(clientRect); - // Assume parent div is containing block and we want to render at its top-leftmost - newSize.x = 0; - newSize.y = 0; - - const borderWidth = - (parseFloat(this.parent.style.borderLeftWidth) || 0) + - (parseFloat(this.parent.style.borderRightWidth) || 0); - const borderHeight = - (parseFloat(this.parent.style.borderTopWidth) || 0) + - (parseFloat(this.parent.style.borderBottomWidth) || 0); - newSize.width -= borderWidth; - newSize.height -= borderHeight; - } else { - clientRect = document.body.getBoundingClientRect(); - newSize = Rectangle.fromClientRect(clientRect); - } - newSize.conformElement(this.root.element); - this.root.resize(newSize); - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/component.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/component.ts deleted file mode 100644 index 9fd9104bc3a1..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/component.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { EventEmitter } from "events"; -import { ISize, Rectangle } from "./geometry"; - -// Composition or inheritance for the below? - -export abstract class Component { - protected size = new Rectangle(0, 0, 0, 0); - private readonly events = new EventEmitter(); - private children: Component[] = []; - - constructor(public element: HTMLDivElement) {} - - public on(event: "click", handler: (e: MouseEvent) => void): this; - public on(event: "keypress" | "keydown", handler: (e: KeyboardEvent) => void): this; - public on(event: "resize", handler: (size: Rectangle) => void): this; - public on(event: string, listener: (...args: any[]) => void): this; - public on(event: string, listener: (...args: any[]) => void): this { - this.events.on(event, listener); - return this; - } - - public emit(event: string, ...args: any[]) { - this.events.emit(event, ...args); - for (const child of this.children) { - child.emit(event, ...args); - } - } - - public getChildren(): Component[] { - // Probably will want a way to avoid providing direct access to the underlying array - return this.children; - } - - /** - * Allows the element to provide a desired size relative to the rectangle provided. By default returns - * the provided size. - */ - public measure(size: ISize): ISize { - return size; - } - - public resize(rectangle: Rectangle) { - this.size = rectangle; - this.resizeCore(rectangle); - this.events.emit("resize", rectangle); - } - - // For the child management functions we may want to just make the dervied class do this. Could help them - // provide better context on their tracked nodes. - - protected addChild(component: Component, index = -1) { - if (index === -1) { - this.children.push(component); - } else { - this.children.splice(index, 0, component); - } - } - - protected removeChild(component: Component) { - const index = this.children.lastIndexOf(component); - if (index !== -1) { - this.children.splice(index, 1); - } - } - - protected removeAllChildren() { - this.children = []; - } - - /** - * Allows derived class to do custom processing based on the resize - */ - protected resizeCore(rectangle: Rectangle) { - return; - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/index.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/index.ts deleted file mode 100644 index 171f3f392a25..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export { distanceSquared, IPoint, Point } from "./point"; -export { Rectangle, Square } from "./rectangle"; -export { ISize } from "./size"; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/point.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/point.ts deleted file mode 100644 index 38dcca7a9169..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/point.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export interface IPoint { - x: number; - y: number; -} - -export function distanceSquared(a: IPoint, b: IPoint) { - const dx = a.x - b.x; - const dy = a.y - b.y; - - return dx * dx + dy * dy; -} - -export class Point implements IPoint { - // Constructor - constructor( - public x: number, - public y: number, - ) {} -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/rectangle.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/rectangle.ts deleted file mode 100644 index b7537b091ab6..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/rectangle.ts +++ /dev/null @@ -1,309 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { ISize } from "./size"; - -export class Rectangle { - public static fromClientRect(cr: ClientRect) { - return new Rectangle(cr.left, cr.top, cr.width, cr.height); - } - - public static conformElementToRect(elm: HTMLElement, rect: Rectangle) { - rect.conformElement(elm); - return elm; - } - - /** - * Size of the rectangle - */ - public get size(): ISize { - return { width: this.width, height: this.height }; - } - - constructor( - public x: number, - public y: number, - public width: number, - public height: number, - ) {} - - public square() { - let len = this.width; - let adj = 0; - if (len > this.height) { - len = this.height; - adj = (this.width - len) / 2; - return new Square(this.x + adj, this.y, len); - } else { - adj = (this.height - len) / 2; - return new Square(this.x, this.y + adj, len); - } - } - - public union(other: Rectangle): Rectangle { - const minX = Math.min(this.x, other.x); - const minY = Math.min(this.y, other.y); - const maxX = Math.max(this.x + this.width, other.x + other.width); - const maxY = Math.max(this.y + this.height, other.y + other.height); - return new Rectangle(minX, minY, maxX - minX, maxY - minY); - } - - public contains(other: Rectangle): boolean { - return ( - other.x >= this.x && - other.x + other.width <= this.x + this.width && - other.y >= this.y && - other.y + other.height <= this.y + this.height - ); - } - - public nipVert(pixels: number) { - return [ - new Rectangle(this.x, this.y, this.width, pixels), - new Rectangle(this.x, this.y + pixels, this.width, this.height - pixels), - ]; - } - - public nipVertBottom(pixels: number) { - return [ - new Rectangle(this.x, this.y, this.width, this.height - pixels), - new Rectangle(this.x, this.y + (this.height - pixels), this.width, pixels), - ]; - } - - public nipVertTopBottom(topPixels: number, bottomPixels: number) { - return [ - new Rectangle(this.x, this.y, this.width, topPixels), - new Rectangle( - this.x, - this.y + topPixels, - this.width, - this.height - topPixels - bottomPixels, - ), - new Rectangle(this.x, this.y + (this.height - bottomPixels), this.width, bottomPixels), - ]; - } - - public nipHoriz(pixels: number) { - return [ - new Rectangle(this.x, this.y, pixels, this.height), - new Rectangle(this.x + pixels, this.y, this.width - pixels, this.height), - ]; - } - - public nipHorizRight(pixels: number) { - return [ - new Rectangle(this.x, this.y, this.width - pixels, this.height), - new Rectangle(this.x + (this.width - pixels), this.y, pixels, this.height), - ]; - } - - public conformElementMaxHeight(elm: HTMLElement) { - elm.style.position = "absolute"; - elm.style.left = `${this.x}px`; - elm.style.width = `${this.width}px`; - elm.style.top = `${this.y}px`; - elm.style.maxHeight = `${this.height}px`; - } - - public conformElementMaxHeightFromBottom(elm: HTMLElement, bottom: number) { - elm.style.position = "absolute"; - elm.style.left = `${this.x}px`; - elm.style.width = `${this.width}px`; - elm.style.bottom = `${bottom}px`; - elm.style.maxHeight = `${this.height}px`; - } - - public conformElementOpenHeight(elm: HTMLElement) { - elm.style.position = "absolute"; - elm.style.left = `${this.x}px`; - elm.style.width = `${this.width}px`; - elm.style.top = `${this.y}px`; - } - - public moveElementToUpperLeft(elm: HTMLElement) { - elm.style.position = "absolute"; - elm.style.left = `${this.x}px`; - elm.style.top = `${this.y}px`; - } - - public conformElement(elm: HTMLElement) { - elm.style.position = "absolute"; - elm.style.left = `${this.x}px`; - elm.style.top = `${this.y}px`; - elm.style.width = `${this.width}px`; - elm.style.height = `${this.height}px`; - return elm; - } - - public inner4(xfactor: number, yfactor: number, widthFactor: number, heightFactor: number) { - const ix = this.x + Math.round(xfactor * this.width); - const iy = this.y + Math.round(yfactor * this.height); - const iw = Math.floor(this.width * widthFactor); - const ih = Math.floor(this.height * heightFactor); - return new Rectangle(ix, iy, iw, ih); - } - - public inner(factor: number) { - const iw = Math.round(factor * this.width); - const ih = Math.round(factor * this.height); - const ix = this.x + Math.floor((this.width - iw) / 2); - const iy = this.y + Math.floor((this.height - ih) / 2); - return new Rectangle(ix, iy, iw, ih); - } - - public innerAbs(pixels: number) { - const iw = this.width - 2 * pixels; - const ih = this.height - 2 * pixels; - const ix = this.x + pixels; - const iy = this.y + pixels; - return new Rectangle(ix, iy, iw, ih); - } - - public proportionalSplitHoriz(...proportionalWidths: number[]) { - let totalPropWidth = 0; - let i: number; - - for (i = 0; i < proportionalWidths.length; i++) { - totalPropWidth += proportionalWidths[i]; - } - - let totalWidth = 0; - const widths: number[] = []; - for (i = 0; i < proportionalWidths.length; i++) { - widths[i] = (proportionalWidths[i] / totalPropWidth) * this.width; - totalWidth += widths[i]; - } - - let extraWidth = this.width - totalWidth; - /* Add back round-off error equally to all rectangles */ - i = 0; - while (extraWidth > 0) { - widths[i]++; - extraWidth--; - if (++i === widths.length) { - i = 0; - } - } - const rects: Rectangle[] = []; - let curX = this.x; - for (i = 0; i < widths.length; i++) { - rects[i] = new Rectangle(curX, this.y, widths[i], this.height); - curX += widths[i]; - } - return rects; - } - - public proportionalSplitVert(...proportionalHeights: number[]): Rectangle[] { - let totalPropHeight = 0; - let i: number; - - for (i = 0; i < proportionalHeights.length; i++) { - totalPropHeight += proportionalHeights[i]; - } - - let totalHeight = 0; - const heights: number[] = []; - for (i = 0; i < proportionalHeights.length; i++) { - heights[i] = (proportionalHeights[i] / totalPropHeight) * this.height; - totalHeight += heights[i]; - } - - let extraHeight = this.height - totalHeight; - /* Add back round-off error equally to all rectangles */ - i = 0; - while (extraHeight > 0) { - heights[i]++; - extraHeight--; - if (++i === heights.length) { - i = 0; - } - } - const rects: Rectangle[] = []; - let curY = this.y; - for (i = 0; i < heights.length; i++) { - rects[i] = new Rectangle(this.x, curY, this.width, heights[i]); - curY += heights[i]; - } - return rects; - } - - public within(x: number, y: number) { - return this.x <= x && this.y <= y && this.x + this.width >= x && this.y + this.height >= y; - } - - public subDivideHorizAbs(width: number) { - const n = Math.ceil(this.width / width); - return this.subDivideHoriz(n); - } - - public subDivideHoriz(n: number) { - const rects: Rectangle[] = []; - - const tileWidth = this.width / n; - let rem = this.width % n; - let tileX = this.x; - for (let i = 0; i < n; i++) { - rects[i] = new Rectangle(tileX, this.y, tileWidth, this.height); - if (rem > 0) { - rects[i].width++; - rem--; - } - tileX += rects[i].width; - } - return rects; - } - - public subDivideVertAbs(height: number, peanutButter = true) { - const n = Math.ceil(this.height / height); - return this.subDivideVert(n, peanutButter); - } - - public subDivideVertAbsEnclosed(height: number, peanutButter = true) { - const n = Math.ceil(this.height / height); - return this.subDivideVertEnclosed(n, peanutButter); - } - - public subDivideVertEnclosed(n: number, peanutButter = true) { - const rects: Rectangle[] = []; - const tileHeight = Math.floor(this.height / n); - let rem = this.height % n; - let tileY = 0; - for (let i = 0; i < n; i++) { - rects[i] = new Rectangle(0, tileY, this.width, tileHeight); - if (peanutButter && rem > 0) { - rects[i].height++; - rem--; - } - tileY += rects[i].height; - } - return rects; - } - - public subDivideVert(n: number, peanutButter = true) { - const rects: Rectangle[] = []; - const tileHeight = Math.floor(this.height / n); - let rem = this.height % n; - let tileY = this.y; - for (let i = 0; i < n; i++) { - rects[i] = new Rectangle(this.x, tileY, this.width, tileHeight); - if (peanutButter && rem > 0) { - rects[i].height++; - rem--; - } - tileY += rects[i].height; - } - return rects; - } -} - -export class Square extends Rectangle { - public len: number; - - constructor(x: number, y: number, len: number) { - super(x, y, len, len); - this.len = len; - } -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/size.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/size.ts deleted file mode 100644 index 2f7278c55673..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/size.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * Size interfaces - */ -export interface ISize { - width: number; - - height: number; -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/index.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/index.ts deleted file mode 100644 index 48930a3bfc20..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export { BrowserContainerHost } from "./browserContainerHost"; -export { Component } from "./component"; -export { distanceSquared, IPoint, ISize, Point, Rectangle, Square } from "./geometry"; -export { removeAllChildren } from "./utils"; diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/utils.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/utils.ts deleted file mode 100644 index 7015891d9d3e..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export function removeAllChildren(element: HTMLElement) { - // Remove any existing children and attach ourselves - while (element.lastChild !== null) { - element.removeChild(element.lastChild); - } -} diff --git a/examples/data-objects/shared-text/src/dataObject.ts b/examples/data-objects/shared-text/src/dataObject.ts deleted file mode 100644 index 08cb16f284d1..000000000000 --- a/examples/data-objects/shared-text/src/dataObject.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; -import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import { ReferenceType, reservedTileLabelsKey } from "@fluidframework/merge-tree"; -import { SharedString } from "@fluidframework/sequence"; - -const textSharedStringId = "text"; - -export class SharedTextDataObject extends DataObject { - public static readonly Name = "@fluid-example/shared-text"; - - public static readonly factory = new DataObjectFactory( - SharedTextDataObject.Name, - SharedTextDataObject, - [SharedString.getFactory()], - {}, - ); - - // It's generally not a good pattern to expose the runtime publicly -- here we do it for legacy reasons. - public get exposedRuntime(): IFluidDataStoreRuntime { - return this.runtime; - } - - private _sharedString: SharedString | undefined; - // It's also generally not a good pattern to expose raw data structures publicly. - public get sharedString(): SharedString { - if (this._sharedString === undefined) { - throw new Error("Shared string not initialized"); - } - return this._sharedString; - } - - protected async initializingFirstTime() { - this._sharedString = SharedString.create(this.runtime); - this._sharedString.insertMarker(0, ReferenceType.Tile, { [reservedTileLabelsKey]: ["pg"] }); - this.root.set(textSharedStringId, this._sharedString.handle); - } - - protected async hasInitialized() { - const sharedStringHandle = this.root.get>(textSharedStringId); - if (sharedStringHandle === undefined) { - throw new Error("Shared string handle not found"); - } - this._sharedString = await sharedStringHandle.get(); - } -} - -export const SharedTextDataStoreFactory = SharedTextDataObject.factory; diff --git a/examples/data-objects/shared-text/src/index.ts b/examples/data-objects/shared-text/src/index.ts deleted file mode 100644 index df0eee5e2c60..000000000000 --- a/examples/data-objects/shared-text/src/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { ContainerViewRuntimeFactory } from "@fluid-example/example-utils"; - -import React from "react"; - -import { SharedTextDataObject } from "./dataObject"; -import { SharedTextReactView } from "./view"; - -const sharedTextViewCallback = (sharedTextDataObject: SharedTextDataObject) => - React.createElement(SharedTextReactView, { sharedTextDataObject }); - -/** - * @internal - */ -export const fluidExport = new ContainerViewRuntimeFactory( - SharedTextDataObject.factory, - sharedTextViewCallback, -); diff --git a/examples/data-objects/shared-text/src/view.tsx b/examples/data-objects/shared-text/src/view.tsx deleted file mode 100644 index 5ca3ecb8902d..000000000000 --- a/examples/data-objects/shared-text/src/view.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import registerDebug from "debug"; -import { performance } from "@fluid-internal/client-utils"; - -import React, { useEffect, useRef } from "react"; - -import { controls, ui } from "./client-ui-lib"; -import { SharedTextDataObject } from "./dataObject"; - -/* eslint-disable import/no-internal-modules, import/no-unassigned-import */ -import "bootstrap/dist/css/bootstrap.min.css"; -import "bootstrap/dist/css/bootstrap-theme.min.css"; -import "../stylesheets/map.css"; -import "../stylesheets/style.css"; -/* eslint-enable import/no-internal-modules, import/no-unassigned-import */ - -const debug = registerDebug("fluid:shared-text"); - -class SharedTextView { - private uiInitialized = false; - - public constructor(private readonly sharedTextDataObject: SharedTextDataObject) {} - - public render(element: HTMLElement) { - if (this.uiInitialized) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.initializeUI(element).catch(debug); - this.uiInitialized = true; - } - - private async initializeUI(div): Promise { - const browserContainerHost = new ui.BrowserContainerHost(); - - const containerDiv = document.createElement("div"); - containerDiv.classList.add("flow-container"); - const container = new controls.FlowContainer( - containerDiv, - "Shared Text", - this.sharedTextDataObject.exposedRuntime, - this.sharedTextDataObject.sharedString, - ); - const theFlow = container.flowView; - browserContainerHost.attach(container, div); - - theFlow.render(0, true); - theFlow.timeToEdit = theFlow.timeToImpression = performance.now(); - - theFlow.setEdit(); - - this.sharedTextDataObject.sharedString.loaded - .then(() => { - theFlow.loadFinished(performance.now()); - debug( - `${ - this.sharedTextDataObject.exposedRuntime.id - } fully loaded: ${performance.now()} `, - ); - }) - .catch((e) => { - console.error(e); - }); - } -} - -export interface ISharedTextReactViewProps { - readonly sharedTextDataObject: SharedTextDataObject; -} - -export const SharedTextReactView: React.FC = ( - props: ISharedTextReactViewProps, -) => { - const { sharedTextDataObject } = props; - const htmlView = useRef(new SharedTextView(sharedTextDataObject)); - const divRef = useRef(null); - useEffect(() => { - if (divRef.current !== null) { - htmlView.current.render(divRef.current); - } - }, [divRef.current]); - // FlowContainer does its own layout that doesn't play nice with normal CSS layout. Stretch the wrapping div - // so it can successfully take the full page height. - return
; -}; diff --git a/examples/data-objects/shared-text/stylesheets/map.css b/examples/data-objects/shared-text/stylesheets/map.css deleted file mode 100644 index 3a0775769853..000000000000 --- a/examples/data-objects/shared-text/stylesheets/map.css +++ /dev/null @@ -1,10 +0,0 @@ -.collab-object { - margin: 15px; - border: black 1px solid; - background: lightblue; -} -.collab-cell { - margin: 15px; - border: black 1px solid; - background: lightcoral; -} diff --git a/examples/data-objects/shared-text/stylesheets/style.css b/examples/data-objects/shared-text/stylesheets/style.css deleted file mode 100644 index 394a0e8ea889..000000000000 --- a/examples/data-objects/shared-text/stylesheets/style.css +++ /dev/null @@ -1,313 +0,0 @@ -.blinking { - animation: blinkrecipe 1s linear 15; -} - -.brieflyBlinking { - animation: blinkrecipe 1s linear 2; -} - -@keyframes blinkrecipe { - 50% { - opacity: 0; - } -} - -#hitPlane { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 2; -} -.drawSurface { - touch-action: none; - /* Disable touch behaviors, like pan and zoom */ - position: absolute; - left: 0px; - top: 0px; - width: 100%; - height: 100%; -} -.selectable { - z-index: 1; -} -.collab-document { - background-color: white; - position: relative; -} -.canvas-chrome { - background-color: dimgray; - position: absolute; - z-index: 11; - padding: 5px; -} -.banner { - color: blue; -} -.stickyNote { - touch-action: none; - user-select: none; - -ms-user-select: none; - position: absolute; - color: darkred; - background-color: yellow; - box-shadow: none; - width: 300px; - height: 300px; - transform-origin: 0px 0px; - z-index: 1; -} -.stickySelected { - box-shadow: 10px 10px 20px #444444; - z-index: 10; -} -.navbar-shared-text { - background-color: #f1f1f1; - min-height: unset; -} -.navbar-shared-text ul { - padding: 5px 15px; -} -@media (min-width: 768px) { - .navbar-shared-text ul { - padding: 5px 0px; - } -} -.navbar-shared-text ul.list-inline { - margin-bottom: 0px; -} -.navbar-fluid { - background-color: #222; -} -.btn-flat { - border: none; - background-color: inherit; - width: 100%; - height: 100%; -} -.btn-palette { - border-radius: 0; - background-color: #333; - width: 50px; - height: 50px; - padding: 5px; - border: none; -} -.btn-palette:hover { - background-color: orangered; -} -.fluid-icon { - background-size: 50%; - background-repeat: no-repeat; - background-position: center center; -} -.dropdown-menu > li > a.color-choice { - width: 100%; - height: 50px; -} -.dropdown-menu > li > a.color-choice:hover, -.dropdown-menu > li > a.color-choice:focus { - background-color: inherit; - background-image: inherit; -} -.dropdown-menu-fluid { - padding: 0 0; -} -.navbar-nav.navbar-palette { - margin: 0; -} -.navbar-nav.navbar-palette > li { - float: left; -} -.typing-details { - margin-top: 25px; -} -@keyframes fadein { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -.login-page { - width: 360px; - padding: 8% 0 0; - margin: auto; -} -.form { - position: relative; - z-index: 1; - background: #ffffff; - max-width: 360px; - margin: 0 auto 100px; - padding: 45px; - text-align: center; - box-shadow: - 0 0 20px 0 rgba(0, 0, 0, 0.2), - 0 5px 5px 0 rgba(0, 0, 0, 0.24); -} -.form input { - font-family: "Roboto", sans-serif; - outline: 0; - background: #f2f2f2; - width: 100%; - border: 0; - margin: 0 0 15px; - padding: 15px; - box-sizing: border-box; - font-size: 14px; -} -.form textarea { - font-family: "Roboto", sans-serif; - outline: 0; - background: #f2f2f2; - width: 100%; - height: 300px; - border: 0; - margin: 0 0 15px; - padding: 15px; - box-sizing: border-box; - font-size: 14px; - overflow-y: auto; - resize: none; - /* enable below lines to make text invisible. */ - -webkit-text-security: disc; - -moz-text-security: disc; - text-security: disc; -} -.form button { - font-family: "Roboto", sans-serif; - text-transform: uppercase; - outline: 0; - background: #4caf50; - width: 100%; - border: 0; - padding: 15px; - color: #ffffff; - font-size: 14px; - -webkit-transition: all 0.3 ease; - transition: all 0.3 ease; - cursor: pointer; -} -.form button:hover, -.form button:active, -.form button:focus { - background: #43a047; -} -.loginbody { - background: #76b852; - font-family: "Roboto", sans-serif; -} -.indicatortext { - color: #fff; - font-weight: bold; - display: none; - /* hide by default */ -} -.form .specialmessage { - margin: 15px 0 0; - color: #b3b3b3; - font-size: 12px; -} -.form .specialmessage a { - color: #4caf50; - text-decoration: none; -} -div.titleletters { - width: 90%; - margin: 0 auto; - text-align: center; -} -.titleletter { - display: inline-block; - font-weight: 900; - font-size: 4em; - margin: 0.2em; - position: relative; - color: #4caf50; - transform-style: preserve-3d; - perspective: 400; - z-index: 1; -} -.titleletter:before, -.titleletter:after { - position: absolute; - content: attr(data-letter); - transform-origin: top left; - top: 0; - left: 0; -} -.titleletter, -.titleletter:before, -.titleletter:after { - transition: all 0.3s ease-in-out; -} -.titleletter:before { - color: #fff; - text-shadow: - -1px 0px 1px rgba(255, 255, 255, 0.8), - 1px 0px 1px rgba(0, 0, 0, 0.8); - z-index: 3; - transform: rotateX(0deg) rotateY(-15deg) rotateZ(0deg); -} -.titleletter:after { - color: rgba(0, 0, 0, 0.11); - z-index: 2; - transform: scale(1.08, 1) rotateX(0deg) rotateY(0deg) rotateZ(0deg) skew(0deg, 1deg); -} -.titleletter:hover:before { - color: #fafafa; - transform: rotateX(0deg) rotateY(-40deg) rotateZ(0deg); -} -.titleletter:hover:after { - transform: scale(1.08, 1) rotateX(0deg) rotateY(40deg) rotateZ(0deg) skew(0deg, 22deg); -} -.status-bar ul { - padding-left: 0px; - display: inline-block; - list-style: none; -} -.status-bar ul li { - display: inline-block; - margin-left: 5px; -} -.title-bar { - display: inline-block; - padding-top: 6px; -} -.flow-container { - touch-action: none; - overflow: hidden; -} -.graph-canvas { - padding-left: 0; - padding-right: 0; - margin-left: auto; - margin-right: auto; - display: block; - width: 800px; -} -#parent-canvas { - width: 100%; - height: 1200px; - overflow: auto; -} -.links line { - stroke: #999; - stroke-opacity: 0.6; -} -.nodes circle { - stroke: #fff; - stroke-width: 1.5px; -} -.slider { - display: inline !important; -} -.component-link { - opacity: 0; -} -.component-link:hover { - opacity: 1; -} diff --git a/examples/data-objects/shared-text/tests/sharedText.test.ts b/examples/data-objects/shared-text/tests/sharedText.test.ts deleted file mode 100644 index 13e421675fc7..000000000000 --- a/examples/data-objects/shared-text/tests/sharedText.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { globals } from "../jest.config"; - -describe("sharedText", () => { - beforeAll(async () => { - // Wait for the page to load first before running any tests - // so this time isn't attributed to the first test - await page.goto(globals.PATH, { waitUntil: "load", timeout: 0 }); - }, 45000); - - beforeEach(async () => { - await page.goto(globals.PATH, { waitUntil: "load" }); - await page.waitForFunction(() => window["fluidStarted"]); - }); - - test("The title of the document is the same for both users", async () => { - const getTitles = async (index: number) => { - return page.evaluate((i: number) => { - const titleElements = document.getElementsByClassName("title-bar"); - const title = titleElements[i] as HTMLDivElement; - if (title) { - return title.innerText; - } - - return ""; - }, index); - }; - - // Get the titles of the two documents and verify they are the same. - const titleLeft = await getTitles(0); - expect(titleLeft).not.toEqual(""); - - const titleRight = await getTitles(1); - expect(titleLeft).toEqual(titleRight); - }); - test("the text typed by one user updates the text for the other user", async () => { - const getText = async (index: number) => { - return page.evaluate((i: number) => { - const titleElements = document.getElementsByClassName("flow-view"); - const title = titleElements[i] as HTMLDivElement; - if (title) { - let text = ""; - // all content is stored in spans, and presence is stored in divs - // we only want content here - title.querySelectorAll("span").forEach((span) => (text += span.innerText)); - return text; - } - - return ""; - }, index); - }; - - const word: string = "sharedTextTest"; - // Issue #5331: Generate synthetic events on the client side to improve stability instead of using page.type - await page.evaluate((word: string) => { - for (const c of word) { - // Type a word in one of the documents. There are two classes with name "flow-view", - // one for each user. This will pick the first class it finds and type in that. - document.body.dispatchEvent( - new KeyboardEvent("keypress", { charCode: c.charCodeAt(0) } as any), - ); - } - }, word); - - // wait for all changes to propagate - await page.waitForFunction(() => window["FluidLoader"].isSynchronized()); - - // The text returned has extra spaces so remove the extra spaces - let textLeft = await getText(0); - expect(textLeft).not.toEqual(""); - textLeft = textLeft.replace(/\s/g, ""); - - let textRight = await getText(1); - expect(textRight).not.toEqual(""); - textRight = textRight.replace(/\s/g, ""); - - // Verify that the text updated for both the users. - expect(textLeft).toEqual(word); - expect(textRight).toEqual(word); - }); -}); diff --git a/examples/data-objects/shared-text/tsconfig.json b/examples/data-objects/shared-text/tsconfig.json deleted file mode 100644 index 4f0f7971fcf1..000000000000 --- a/examples/data-objects/shared-text/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "@fluidframework/build-common/ts-common-config.json", - "compilerOptions": { - "module": "esnext", - "outDir": "lib", - "jsx": "react", - "types": [ - "react", - "react-dom", - "jest", - "puppeteer", - "jest-environment-puppeteer", - "expect-puppeteer", - ], - }, - "include": ["src/**/*.ts"], -} diff --git a/examples/data-objects/shared-text/webpack.config.js b/examples/data-objects/shared-text/webpack.config.js deleted file mode 100644 index 6fdd776f3521..000000000000 --- a/examples/data-objects/shared-text/webpack.config.js +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ -const fluidRoute = require("@fluid-tools/webpack-fluid-loader"); -const path = require("path"); -const { merge } = require("webpack-merge"); -const pkg = require("./package.json"); -const webpack = require("webpack"); -// var Visualizer = require('webpack-visualizer-plugin'); -// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); - -module.exports = (env) => { - const isProduction = env?.production; - return merge( - { - entry: "./src/index.ts", - resolve: { - extensions: [".mjs", ".ts", ".tsx", ".js"], - fallback: { - dgram: false, - fs: false, - net: false, - tls: false, - child_process: false, - }, - }, - devtool: "source-map", - mode: "production", - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - { - loader: "ts-loader", - options: { - compilerOptions: { - module: "esnext", - }, - }, - }, - ], - exclude: /node_modules/, - }, - { - test: /\.js$/, - use: [require.resolve("source-map-loader")], - enforce: "pre", - }, - { - test: /\.css$/, - use: [ - "style-loader", // creates style nodes from JS strings - "css-loader", // translates CSS into CommonJS - ], - }, - { - test: /\.scss$/, - use: [ - "style-loader", // creates style nodes from JS strings - "css-loader", // translates CSS into CommonJS - "sass-loader", // compiles Sass to CSS, using Node Sass by default - ], - }, - { - test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/, - loader: "url-loader", - options: { - limit: 10000, - }, - }, - { - test: /\.html$/, - loader: "html-loader", - }, - ], - }, - devServer: { devMiddleware: { stats: "minimal" } }, - output: { - filename: "[name].bundle.js", - chunkFilename: "[name].async.js", - path: path.resolve(__dirname, "dist"), - publicPath: "/dist/", - library: "[name]", - // https://github.com/webpack/webpack/issues/5767 - // https://github.com/webpack/webpack/issues/7939 - devtoolNamespace: "shared-text", - libraryTarget: "umd", - globalObject: "self", - }, - plugins: [ - new webpack.ProvidePlugin({ - process: "process/browser", - }), - // new MonacoWebpackPlugin() - // new BundleAnalyzerPlugin() - ], - }, - isProduction ? require("./webpack.prod") : require("./webpack.dev"), - fluidRoute.devServerConfig(__dirname, env), - ); -}; diff --git a/examples/data-objects/shared-text/webpack.dev.js b/examples/data-objects/shared-text/webpack.dev.js deleted file mode 100644 index c5b13a83bad8..000000000000 --- a/examples/data-objects/shared-text/webpack.dev.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - mode: "development", - devtool: "inline-source-map", -}; diff --git a/examples/data-objects/shared-text/webpack.prod.js b/examples/data-objects/shared-text/webpack.prod.js deleted file mode 100644 index 2273d173d964..000000000000 --- a/examples/data-objects/shared-text/webpack.prod.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - mode: "production", - devtool: "source-map", -}; diff --git a/examples/data-objects/smde/package.json b/examples/data-objects/smde/package.json index d672d21ab3e3..62827e199ce4 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", @@ -48,7 +48,6 @@ "@fluidframework/datastore": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", "@fluidframework/map": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", "@fluidframework/request-handler": "workspace:~", "@fluidframework/runtime-definitions": "workspace:~", "@fluidframework/runtime-utils": "workspace:~", diff --git a/examples/data-objects/smde/src/smde.ts b/examples/data-objects/smde/src/smde.ts index 03a377f4da95..3fd1698ef60c 100644 --- a/examples/data-objects/smde/src/smde.ts +++ b/examples/data-objects/smde/src/smde.ts @@ -8,13 +8,12 @@ import { assert } from "@fluidframework/core-utils"; import { IFluidLoadable, IFluidHandle } from "@fluidframework/core-interfaces"; import { FluidDataStoreRuntime, FluidObjectHandle } from "@fluidframework/datastore"; import { ISharedMap, SharedMap } from "@fluidframework/map"; -import { ReferenceType, reservedTileLabelsKey } from "@fluidframework/merge-tree"; import { IFluidDataStoreContext, IFluidDataStoreFactory, } from "@fluidframework/runtime-definitions"; import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import { SharedString } from "@fluidframework/sequence"; +import { SharedString, ReferenceType, reservedTileLabelsKey } from "@fluidframework/sequence"; // eslint-disable-next-line import/no-internal-modules, import/no-unassigned-import import "simplemde/dist/simplemde.min.css"; diff --git a/examples/data-objects/smde/src/smdeView.tsx b/examples/data-objects/smde/src/smdeView.tsx index d95308ba1e42..61096898968a 100644 --- a/examples/data-objects/smde/src/smdeView.tsx +++ b/examples/data-objects/smde/src/smdeView.tsx @@ -4,13 +4,13 @@ */ import { + getTextAndMarkers, MergeTreeDeltaType, TextSegment, ReferenceType, reservedTileLabelsKey, Marker, -} from "@fluidframework/merge-tree"; -import { getTextAndMarkers } from "@fluidframework/sequence"; +} from "@fluidframework/sequence"; import React, { useEffect, useRef } from "react"; import SimpleMDE from "simplemde"; diff --git a/examples/data-objects/table-document/package.json b/examples/data-objects/table-document/package.json index 11c7c5a4211d..a8a1123673ab 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Chaincode component containing a table's data", "homepage": "https://fluidframework.com", "repository": { @@ -56,7 +56,6 @@ "@fluidframework/core-interfaces": "workspace:~", "@fluidframework/core-utils": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", - "@fluidframework/merge-tree": "workspace:~", "@fluidframework/protocol-definitions": "^3.0.0", "@fluidframework/request-handler": "workspace:~", "@fluidframework/runtime-definitions": "workspace:~", diff --git a/examples/data-objects/table-document/src/cellrange.ts b/examples/data-objects/table-document/src/cellrange.ts index deb7e6998d8b..18abcf3ddf5a 100644 --- a/examples/data-objects/table-document/src/cellrange.ts +++ b/examples/data-objects/table-document/src/cellrange.ts @@ -4,8 +4,7 @@ */ import { assert } from "@fluidframework/core-utils"; -import { ReferencePosition } from "@fluidframework/merge-tree"; -import { SequenceInterval } from "@fluidframework/sequence"; +import { SequenceInterval, ReferencePosition } from "@fluidframework/sequence"; const rangeExpr = /([A-Za-z]+)(\d+):([A-Za-z]+)(\d+)/; diff --git a/examples/data-objects/table-document/src/document.ts b/examples/data-objects/table-document/src/document.ts index 5f7974c598df..6469c921e265 100644 --- a/examples/data-objects/table-document/src/document.ts +++ b/examples/data-objects/table-document/src/document.ts @@ -5,11 +5,12 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IEvent, IFluidHandle } from "@fluidframework/core-interfaces"; -import { ICombiningOp, ReferencePosition, PropertySet } from "@fluidframework/merge-tree"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { IntervalType, SequenceDeltaEvent, + ReferencePosition, + PropertySet, SharedString, createEndpointIndex, } from "@fluidframework/sequence"; @@ -112,26 +113,16 @@ export class TableDocument extends DataObject<{ Events: ITableDocumentEvents }> return component; } - public annotateRows( - startRow: number, - endRow: number, - properties: PropertySet, - op?: ICombiningOp, - ) { - this.rows.annotateRange(startRow, endRow, properties, op); + public annotateRows(startRow: number, endRow: number, properties: PropertySet) { + this.rows.annotateRange(startRow, endRow, properties); } public getRowProperties(row: number): PropertySet { return this.rows.getPropertiesAtPosition(row); } - public annotateCols( - startCol: number, - endCol: number, - properties: PropertySet, - op?: ICombiningOp, - ) { - this.cols.annotateRange(startCol, endCol, properties, op); + public annotateCols(startCol: number, endCol: number, properties: PropertySet) { + this.cols.annotateRange(startCol, endCol, properties); } public getColProperties(col: number): PropertySet { diff --git a/examples/data-objects/table-document/src/interception/tableWithInterception.ts b/examples/data-objects/table-document/src/interception/tableWithInterception.ts index 297880c11afc..54d06043efd4 100644 --- a/examples/data-objects/table-document/src/interception/tableWithInterception.ts +++ b/examples/data-objects/table-document/src/interception/tableWithInterception.ts @@ -4,7 +4,7 @@ */ import { assert } from "@fluidframework/core-utils"; -import { PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/sequence"; import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions"; import { ITable, TableDocumentItem } from "../table"; import { TableDocument } from "../document"; diff --git a/examples/data-objects/table-document/src/slice.ts b/examples/data-objects/table-document/src/slice.ts index d7d74d8a97d2..6d8edb7537cb 100644 --- a/examples/data-objects/table-document/src/slice.ts +++ b/examples/data-objects/table-document/src/slice.ts @@ -5,8 +5,7 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { ICombiningOp, PropertySet } from "@fluidframework/merge-tree"; -import { handleFromLegacyUri } from "@fluidframework/request-handler"; +import { PropertySet } from "@fluidframework/sequence"; import { CellRange } from "./cellrange"; import { TableSliceType } from "./componentTypes"; import { ConfigKey } from "./configKey"; @@ -70,15 +69,10 @@ export class TableSlice extends DataObject<{ InitialState: ITableSliceConfig }> this.doc.setCellValue(row, col, value, properties); } - public annotateRows( - startRow: number, - endRow: number, - properties: PropertySet, - op?: ICombiningOp, - ) { + public annotateRows(startRow: number, endRow: number, properties: PropertySet) { this.validateInSlice(startRow, undefined); this.validateInSlice(endRow - 1, undefined); - this.doc.annotateRows(startRow, endRow, properties, op); + this.doc.annotateRows(startRow, endRow, properties); } public getRowProperties(row: number): PropertySet { @@ -86,15 +80,10 @@ export class TableSlice extends DataObject<{ InitialState: ITableSliceConfig }> return this.doc.getRowProperties(row); } - public annotateCols( - startCol: number, - endCol: number, - properties: PropertySet, - op?: ICombiningOp, - ) { + public annotateCols(startCol: number, endCol: number, properties: PropertySet) { this.validateInSlice(undefined, startCol); this.validateInSlice(undefined, endCol - 1); - this.doc.annotateCols(startCol, endCol, properties, op); + this.doc.annotateCols(startCol, endCol, properties); } public getColProperties(col: number): PropertySet { @@ -135,10 +124,13 @@ export class TableSlice extends DataObject<{ InitialState: ITableSliceConfig }> this.root.set(ConfigKey.docId, initialState.docId); this.root.set(ConfigKey.name, initialState.name); - this.maybeDoc = await handleFromLegacyUri( - `/${initialState.docId}`, - this.context.containerRuntime, - ).get(); + const response = await this.context.IFluidHandleContext.resolveHandle({ + url: `/${initialState.docId}`, + }); + if (response.status !== 200 || response.mimeType !== "fluid/object") { + throw new Error("Could not resolve handle"); + } + this.maybeDoc = response.value; this.root.set(initialState.docId, this.maybeDoc.handle); await this.ensureDoc(); this.createValuesRange( diff --git a/examples/data-objects/table-document/src/table.ts b/examples/data-objects/table-document/src/table.ts index 370720db77dd..7f29de878fd5 100644 --- a/examples/data-objects/table-document/src/table.ts +++ b/examples/data-objects/table-document/src/table.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ICombiningOp, PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/sequence"; export type TableDocumentItem = any; @@ -18,9 +18,9 @@ export interface ITable { getCellValue(row: number, col: number): TableDocumentItem; setCellValue(row: number, col: number, value: TableDocumentItem, properties?: PropertySet); - annotateRows(startRow: number, endRow: number, properties: PropertySet, op?: ICombiningOp); + annotateRows(startRow: number, endRow: number, properties: PropertySet); getRowProperties(row: number): PropertySet; - annotateCols(startCol: number, endCol: number, properties: PropertySet, op?: ICombiningOp); + annotateCols(startCol: number, endCol: number, properties: PropertySet); getColProperties(col: number): PropertySet; annotateCell(row: number, col: number, properties: PropertySet); getCellProperties(row: number, col: number): PropertySet; diff --git a/examples/data-objects/table-document/src/test/tableWithInterception.spec.mts b/examples/data-objects/table-document/src/test/tableWithInterception.spec.mts index cd194e84e18d..e1a30c5e3de3 100644 --- a/examples/data-objects/table-document/src/test/tableWithInterception.spec.mts +++ b/examples/data-objects/table-document/src/test/tableWithInterception.spec.mts @@ -4,7 +4,7 @@ */ import { strict as assert } from "assert"; -import { PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/sequence"; import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions"; import { ITestObjectProvider, getContainerEntryPointBackCompat } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; diff --git a/examples/data-objects/table-view/package.json b/examples/data-objects/table-view/package.json index 446d793ead11..6e74973f5db0 100644 --- a/examples/data-objects/table-view/package.json +++ b/examples/data-objects/table-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/table-view", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Chaincode component that provides a view for a table-document.", "homepage": "https://fluidframework.com", @@ -51,6 +51,7 @@ "@fluidframework/sequence": "workspace:~", "@fluidframework/view-interfaces": "workspace:~", "@tiny-calc/micro": "0.0.0-alpha.5", + "@tiny-calc/nano": "0.0.0-alpha.5", "react": "^17.0.1" }, "devDependencies": { diff --git a/examples/data-objects/table-view/src/grid.ts b/examples/data-objects/table-view/src/grid.ts index 748397f36c69..47d22d59dba6 100644 --- a/examples/data-objects/table-view/src/grid.ts +++ b/examples/data-objects/table-view/src/grid.ts @@ -6,6 +6,7 @@ import { colIndexToName } from "@fluid-example/table-document"; import { SharedMatrix } from "@fluidframework/matrix"; import { ISheetlet, createSheetletProducer } from "@tiny-calc/micro"; +import type { IMatrixProducer } from "@tiny-calc/nano"; import { BorderRect } from "./borderstyle"; import * as styles from "./index.css"; @@ -22,6 +23,11 @@ const enum KeyCode { arrowDown = "ArrowDown", // 40 } +// Extract Value type from createSheetletProducer requirements. (Value is not exported.) +type GridContentType = Parameters[0] extends IMatrixProducer + ? T + : never; + export class GridView { private get numRows() { return this.matrix.rowCount; @@ -73,7 +79,7 @@ export class GridView { } constructor( - private readonly matrix: SharedMatrix, + private readonly matrix: SharedMatrix, private readonly getFormula: () => string, private readonly setFormula: (val: string) => void, private readonly setSelectionSummary: (val: string) => void, diff --git a/packages/loader/location-redirection-utils/api-extractor.json b/examples/data-objects/todo/api-extractor.json similarity index 78% rename from packages/loader/location-redirection-utils/api-extractor.json rename to examples/data-objects/todo/api-extractor.json index 3cd83b2483bb..34b78596056c 100644 --- a/packages/loader/location-redirection-utils/api-extractor.json +++ b/examples/data-objects/todo/api-extractor.json @@ -1,7 +1,4 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "../../../common/build/build-common/api-extractor-base.json", - "dtsRollup": { - "enabled": true - } + "extends": "../../../common/build/build-common/api-extractor-base.json" } diff --git a/examples/data-objects/todo/package.json b/examples/data-objects/todo/package.json index 8b0f1e99879b..8089f1c9a040 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple todo canvas.", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/webflow/package.json b/examples/data-objects/webflow/package.json index 0519d992f1f4..b93bb725ae5e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Collaborative markdown editor.", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/webflow/src/clipboard/paste.ts b/examples/data-objects/webflow/src/clipboard/paste.ts index 3df400e23418..5231fe8187cb 100644 --- a/examples/data-objects/webflow/src/clipboard/paste.ts +++ b/examples/data-objects/webflow/src/clipboard/paste.ts @@ -50,7 +50,6 @@ function pasteChildren(doc: FlowDocument, root: Node, position: number) { const tag = el.tagName as TagName; const emitTag = !ignoredTags.includes(tag); if (emitTag) { - doc.insertTags([tag], _position); doc.setAttr( _position, _position + 1, diff --git a/examples/data-objects/webflow/src/document/index.ts b/examples/data-objects/webflow/src/document/index.ts index 969132efe1d5..34a9c4604115 100644 --- a/examples/data-objects/webflow/src/document/index.ts +++ b/examples/data-objects/webflow/src/document/index.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { IEvent, IFluidHandle } from "@fluidframework/core-interfaces"; import { @@ -13,23 +11,9 @@ import { } from "@fluidframework/data-object-base"; import { createDetachedLocalReferencePosition, - createInsertSegmentOp, createRemoveRangeOp, IMergeTreeRemoveMsg, - ISegment, - LocalReferencePosition, - Marker, - MergeTreeDeltaType, - PropertySet, - ReferencePosition, - ReferenceType, - refGetRangeLabels, refGetTileLabels, - refHasRangeLabels, - reservedMarkerIdKey, - reservedRangeLabelsKey, - reservedTileLabelsKey, - TextSegment, } from "@fluidframework/merge-tree"; import { IFluidDataStoreContext, @@ -40,9 +24,18 @@ import { SharedStringSegment, SequenceMaintenanceEvent, SequenceDeltaEvent, + ISegment, + LocalReferencePosition, + Marker, + MergeTreeDeltaType, + PropertySet, + ReferencePosition, + ReferenceType, + reservedTileLabelsKey, + TextSegment, } from "@fluidframework/sequence"; import { ISharedDirectory, SharedDirectory } from "@fluidframework/map"; -import { clamp, emptyArray, randomId, TagName, TokenList } from "../util/index.js"; +import { clamp, TagName, TokenList } from "../util/index.js"; import { IHTMLAttributes } from "../util/attr.js"; import { documentType } from "../package.js"; import { debug } from "./debug.js"; @@ -86,27 +79,11 @@ export const getDocSegmentKind = (segment: ISegment): DocSegmentKind => { const markerType = segment.refType; switch (markerType) { case ReferenceType.Tile: - case ReferenceType.Tile | ReferenceType.NestBegin: - const hasRangeLabels = refHasRangeLabels(segment); - const kind = ( - hasRangeLabels ? refGetRangeLabels(segment)[0] : refGetTileLabels(segment)[0] - ) as DocSegmentKind; - + const kind = refGetTileLabels(segment)[0] as DocSegmentKind; assert(tilesAndRanges.has(kind), `Unknown tile/range label.`); return kind; default: - assert( - markerType === (ReferenceType.Tile | ReferenceType.NestEnd), - "unexpected marker type", - ); - - // Ensure that 'nestEnd' range label matches the 'beginTags' range label (otherwise it - // will not close the range.) - assert( - refGetRangeLabels(segment)[0] === DocSegmentKind.beginTags, - `Unknown refType '${markerType}'.`, - ); return DocSegmentKind.endTags; } } @@ -192,10 +169,6 @@ export class FlowDocument extends LazyLoadedDataObject { - const tags = this.sharedString.getStackContext(position, [DocSegmentKind.beginTags])[ - DocSegmentKind.beginTags - ]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return tags?.items || emptyArray; - } - public getStart(marker: Marker) { return this.getOppositeMarker(marker, /* "end".length = */ 3, "begin"); } diff --git a/examples/data-objects/webflow/src/document/segmentspan.ts b/examples/data-objects/webflow/src/document/segmentspan.ts index 1921174778b6..8597aebcf9df 100644 --- a/examples/data-objects/webflow/src/document/segmentspan.ts +++ b/examples/data-objects/webflow/src/document/segmentspan.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ISegment } from "@fluidframework/merge-tree"; +import { ISegment } from "@fluidframework/sequence"; export class SegmentSpan { public get segments(): readonly ISegment[] { diff --git a/examples/data-objects/webflow/src/editor/caret.ts b/examples/data-objects/webflow/src/editor/caret.ts index eddfa9ad6243..bf1bbc87bc19 100644 --- a/examples/data-objects/webflow/src/editor/caret.ts +++ b/examples/data-objects/webflow/src/editor/caret.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { LocalReferencePosition, ReferencePosition } from "@fluidframework/merge-tree"; +import { LocalReferencePosition, ReferencePosition } from "@fluidframework/sequence"; import { DocSegmentKind, getDocSegmentKind } from "../document/index.js"; import { clamp, Dom, hasTagName, TagName } from "../util/index.js"; import { updateRef } from "../util/localref.js"; diff --git a/examples/data-objects/webflow/src/host/webflowView.tsx b/examples/data-objects/webflow/src/host/webflowView.tsx index e088c7bf735c..fcc86a09e7cc 100644 --- a/examples/data-objects/webflow/src/host/webflowView.tsx +++ b/examples/data-objects/webflow/src/host/webflowView.tsx @@ -52,10 +52,6 @@ export const WebflowView: React.FC = (props: IWebflowViewProp const { start, end } = editor.selection; return start < end; }; - const insertTags = (tags: TagName[]) => { - const selection = editor.selection; - flowDocument.insertTags(tags, selection.start, selection.end); - }; const setFormat = (tag: TagName) => { const { end } = editor.selection; @@ -149,13 +145,6 @@ export const WebflowView: React.FC = (props: IWebflowViewProp setFormat(TagName.h6); }, }, - { - name: "ol", - enabled: always, - exec: () => { - insertTags([TagName.ol, TagName.li]); - }, - }, { name: "p", enabled: always, @@ -170,13 +159,6 @@ export const WebflowView: React.FC = (props: IWebflowViewProp switchFormatter(htmlFormatter); }, }, - { - name: "ul", - enabled: always, - exec: () => { - insertTags([TagName.ul, TagName.li]); - }, - }, { name: "red", enabled: always, diff --git a/examples/data-objects/webflow/src/html/css.ts b/examples/data-objects/webflow/src/html/css.ts index 93ee23f1f7b5..c65e9cf74259 100644 --- a/examples/data-objects/webflow/src/html/css.ts +++ b/examples/data-objects/webflow/src/html/css.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ISegment } from "@fluidframework/merge-tree"; +import { ISegment } from "@fluidframework/sequence"; import { getCss } from "../document/index.js"; import { areStringsEquivalent } from "../util/index.js"; diff --git a/examples/data-objects/webflow/src/html/formatters.ts b/examples/data-objects/webflow/src/html/formatters.ts index 18564218f918..e62e657e347f 100644 --- a/examples/data-objects/webflow/src/html/formatters.ts +++ b/examples/data-objects/webflow/src/html/formatters.ts @@ -4,7 +4,7 @@ */ import { assert } from "@fluidframework/core-utils"; -import { Marker, TextSegment } from "@fluidframework/merge-tree"; +import { Marker, TextSegment } from "@fluidframework/sequence"; import { DocSegmentKind, getCss, getDocSegmentKind } from "../document/index.js"; import { emptyObject, TagName } from "../util/index.js"; import { getAttrs, syncAttrs } from "../util/attr.js"; diff --git a/examples/data-objects/webflow/src/test/flowdocument.spec.ts b/examples/data-objects/webflow/src/test/flowdocument.spec.ts index d9f3b9a023d1..a0bfc619b454 100644 --- a/examples/data-objects/webflow/src/test/flowdocument.spec.ts +++ b/examples/data-objects/webflow/src/test/flowdocument.spec.ts @@ -3,119 +3,8 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; -import { Marker, ReferenceType } from "@fluidframework/merge-tree"; -import { ITestObjectProvider, getContainerEntryPointBackCompat } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; -import { FlowDocument } from "../document/index.js"; -import { TagName } from "../util/index.js"; describeCompat("FlowDocument", "LoaderCompat", (getTestObjectProvider) => { - let doc: FlowDocument; - - let provider: ITestObjectProvider; - beforeEach(async () => { - provider = getTestObjectProvider(); - const container = await provider.createContainer(FlowDocument.getFactory()); - doc = await getContainerEntryPointBackCompat(container); - }); - - function expect(expected: string) { - assert.strictEqual(doc.toString(), expected); - } - - function expectTags(start: number, end = start + 1, ...expected: string[][]) { - for (let i = start; i < end; i++) { - const actual = doc.getTags(i).map((marker) => { - assert.strictEqual(marker.refType, ReferenceType.NestBegin | ReferenceType.Tile); - return marker.properties.tags as string[]; - }); - assert.deepStrictEqual(actual, expected); - } - } - - function verifyEnds(start: number, end: number) { - const { segment: startSeg } = doc.getSegmentAndOffset(start); - const { segment: endSeg } = doc.getSegmentAndOffset(end); - - assert.strictEqual(doc.getStart(endSeg as Marker), startSeg); - assert.strictEqual(doc.getEnd(startSeg as Marker), endSeg); - } - - function insertTags(tags: string[], start: number, end?: number) { - doc.insertTags(tags as TagName[], start, end); - } - - describe("tags", () => { - describe("insertTags", () => { - it("insert tag into empty", () => { - insertTags(["t"], 0); - expect(""); - verifyEnds(0, 1); - expectTags(0, 1, ["t"]); - expectTags(1); - }); - it("insert tag around text", () => { - doc.insertText(0, "012"); - expectTags(0, doc.length); - insertTags(["a", "b"], 1, 2); - expect("012"); - verifyEnds(1, 3); - expectTags(0); - expectTags(1, 3, ["a", "b"]); - expectTags(3, 4); - }); - }); - describe("removeRange", () => { - describe("removing start implicitly removes end", () => { - it("'[]' -> ''", () => { - insertTags(["t"], 0, 0); - expect(""); - - doc.remove(0, 1); - expect(""); - }); - it("'0[12]34' -> '034'", () => { - doc.insertText(0, "01234"); - insertTags(["a", "b"], 2, 4); - expect("01234"); - - doc.remove(1, 4); - expect("034"); - }); - }); - describe("preserving start implicitly preserves end", () => { - it("'[]' -> ''", () => { - insertTags(["t"], 0, 0); - expect(""); - - doc.remove(1, 2); - expect(""); - }); - it("'01[]2' -> '01[]2'", () => { - doc.insertText(0, "012"); - insertTags(["t"], 1, 2); - expect("012"); - - doc.remove(3, 4); - expect("012"); - }); - it("'0[1]' -> '0'", () => { - doc.insertText(0, "01"); - insertTags(["t"], 1, 2); - expect("01"); - - doc.remove(2, 4); - expect("0"); - }); - }); - }); - describe("ReferencePosition after last position", () => { - it("can create", () => { - const localRef = doc.addLocalRef(doc.length); - assert.strictEqual(doc.localRefToPosition(localRef), doc.length); - doc.removeLocalRef(localRef); - }); - }); - }); + beforeEach(async () => {}); }); diff --git a/examples/data-objects/webflow/src/test/segmentspan.spec.ts b/examples/data-objects/webflow/src/test/segmentspan.spec.ts index 215c8bc16a59..f74c76e25887 100644 --- a/examples/data-objects/webflow/src/test/segmentspan.spec.ts +++ b/examples/data-objects/webflow/src/test/segmentspan.spec.ts @@ -4,7 +4,7 @@ */ import { strict as assert } from "assert"; -import { TextSegment } from "@fluidframework/merge-tree"; +import { TextSegment } from "@fluidframework/sequence"; import { ITestObjectProvider, getContainerEntryPointBackCompat } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; import { FlowDocument } from "../document/index.js"; diff --git a/examples/data-objects/webflow/src/util/attr.ts b/examples/data-objects/webflow/src/util/attr.ts index f769a1c697f3..b5a39565c567 100644 --- a/examples/data-objects/webflow/src/util/attr.ts +++ b/examples/data-objects/webflow/src/util/attr.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ISegment } from "@fluidframework/merge-tree"; +import { ISegment } from "@fluidframework/sequence"; import { areStringsEquivalent } from "./string.js"; import { emptyObject } from "./index.js"; diff --git a/examples/data-objects/webflow/src/util/localref.ts b/examples/data-objects/webflow/src/util/localref.ts index 747619a68550..f52b6b77f6a4 100644 --- a/examples/data-objects/webflow/src/util/localref.ts +++ b/examples/data-objects/webflow/src/util/localref.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { LocalReferencePosition } from "@fluidframework/merge-tree"; +import { LocalReferencePosition } from "@fluidframework/sequence"; import { debug } from "../document/debug.js"; import { FlowDocument } from "../document/index.js"; diff --git a/examples/data-objects/webflow/src/util/segment.ts b/examples/data-objects/webflow/src/util/segment.ts index b4930cba3883..5ccb3ea4ab32 100644 --- a/examples/data-objects/webflow/src/util/segment.ts +++ b/examples/data-objects/webflow/src/util/segment.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { ISegment } from "@fluidframework/merge-tree"; +import { ISegment } from "@fluidframework/sequence"; export function getSegmentRange(position: number, segment: ISegment, startOffset = 0) { const start = position - Math.max(startOffset, 0); diff --git a/examples/data-objects/webflow/src/view/layout.ts b/examples/data-objects/webflow/src/view/layout.ts index 0d522f064c77..207ad21560cc 100644 --- a/examples/data-objects/webflow/src/view/layout.ts +++ b/examples/data-objects/webflow/src/view/layout.ts @@ -6,13 +6,13 @@ // eslint-disable-next-line import/no-nodejs-modules import assert from "assert"; import { EventEmitter } from "events"; +import { MergeTreeMaintenanceType } from "@fluidframework/merge-tree"; import { + SequenceEvent, ISegment, ReferencePosition, - MergeTreeMaintenanceType, LocalReferencePosition, -} from "@fluidframework/merge-tree"; -import { SequenceEvent } from "@fluidframework/sequence"; +} from "@fluidframework/sequence"; import { FlowDocument } from "../document/index.js"; import { clamp, diff --git a/examples/external-data/package.json b/examples/external-data/package.json index 8423e9b658a7..177d2e1413d5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Integrating an external data source with Fluid data.", "homepage": "https://fluidframework.com", diff --git a/examples/utils/bundle-size-tests/package.json b/examples/utils/bundle-size-tests/package.json index b4b991c2277c..3f30642bfc49 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "A package for understanding the bundle size of Fluid Framework", "homepage": "https://fluidframework.com", diff --git a/examples/utils/example-utils/api-report/example-utils.api.md b/examples/utils/example-utils/api-report/example-utils.api.md index a6029bd9cd44..369c79e73d6d 100644 --- a/examples/utils/example-utils/api-report/example-utils.api.md +++ b/examples/utils/example-utils/api-report/example-utils.api.md @@ -22,7 +22,6 @@ import { IFluidModuleWithDetails } from '@fluidframework/container-definitions'; import { IFluidMountableView } from '@fluidframework/view-interfaces'; import { ILoaderProps } from '@fluidframework/container-loader'; import type { IRequest } from '@fluidframework/core-interfaces'; -import type { IResponse } from '@fluidframework/core-interfaces'; import { IRuntime } from '@fluidframework/container-definitions'; import { IRuntimeFactory } from '@fluidframework/container-definitions'; import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; @@ -174,9 +173,6 @@ export interface IVersionedModel { readonly version: string; } -// @internal @deprecated -export const makeModelRequestHandler: (modelMakerCallback: ModelMakerCallback) => (request: IRequest, runtime: IContainerRuntime) => Promise; - // @internal export type MigrationState = "collaborating" | "stopping" | "migrating" | "migrated"; @@ -249,9 +245,6 @@ export class ModelLoader implements IModelLoader { supportsVersion(version: string): Promise; } -// @internal -export type ModelMakerCallback = (runtime: IContainerRuntime, container: IContainer) => Promise; - // @internal export type SameContainerMigrationState = "collaborating" | "proposingMigration" | "stoppingCollaboration" | "proposingV2Code" | "waitingForV2ProposalCompletion" | "readyForMigration" | "uploadingV2Summary" | "submittingV2Summary" | "migrated"; diff --git a/examples/utils/example-utils/package.json b/examples/utils/example-utils/package.json index 02be558765d4..b0cedc9328d6 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Shared utilities used by examples.", "homepage": "https://fluidframework.com", diff --git a/examples/utils/example-utils/src/index.ts b/examples/utils/example-utils/src/index.ts index 67685ac8134b..5ef83230f888 100644 --- a/examples/utils/example-utils/src/index.ts +++ b/examples/utils/example-utils/src/index.ts @@ -38,10 +38,8 @@ export { Migrator, SameContainerMigrator } from "./migrator"; export { IDetachedModel, IModelLoader, - makeModelRequestHandler, ModelContainerRuntimeFactory, ModelLoader, - ModelMakerCallback, SessionStorageModelLoader, StaticCodeLoader, TinyliciousModelLoader, diff --git a/examples/utils/example-utils/src/modelLoader/index.ts b/examples/utils/example-utils/src/modelLoader/index.ts index 3a0f88185f78..b86e83265be7 100644 --- a/examples/utils/example-utils/src/modelLoader/index.ts +++ b/examples/utils/example-utils/src/modelLoader/index.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. */ -export { IDetachedModel, IModelLoader, ModelMakerCallback } from "./interfaces"; +export { IDetachedModel, IModelLoader } from "./interfaces"; export { ModelContainerRuntimeFactory, IModelContainerRuntimeEntryPoint, } from "./modelContainerRuntimeFactory"; -export { makeModelRequestHandler, ModelLoader } from "./modelLoader"; +export { ModelLoader } from "./modelLoader"; export { SessionStorageModelLoader } from "./sessionStorageModelLoader"; export { StaticCodeLoader } from "./staticCodeLoader"; export { TinyliciousModelLoader } from "./tinyliciousModelLoader"; diff --git a/examples/utils/example-utils/src/modelLoader/interfaces.ts b/examples/utils/example-utils/src/modelLoader/interfaces.ts index e112fb1c8af4..bc7a9456e0c2 100644 --- a/examples/utils/example-utils/src/modelLoader/interfaces.ts +++ b/examples/utils/example-utils/src/modelLoader/interfaces.ts @@ -3,9 +3,6 @@ * Licensed under the MIT License. */ -import type { IContainer } from "@fluidframework/container-definitions"; -import type { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; - /** * Object returned from calling IModelLoader.createDetached(). * @internal @@ -56,12 +53,3 @@ export interface IModelLoader { */ loadExistingPaused(id: string, sequenceNumber: number): Promise; } - -/** - * The callback signature that the container author will provide. It must return a promise for the container's model. - * @internal - */ -export type ModelMakerCallback = ( - runtime: IContainerRuntime, - container: IContainer, -) => Promise; diff --git a/examples/utils/example-utils/src/modelLoader/modelLoader.ts b/examples/utils/example-utils/src/modelLoader/modelLoader.ts index 6f61a4bfee69..9aa8bcd8270f 100644 --- a/examples/utils/example-utils/src/modelLoader/modelLoader.ts +++ b/examples/utils/example-utils/src/modelLoader/modelLoader.ts @@ -9,10 +9,8 @@ import { type IHostLoader, } from "@fluidframework/container-definitions"; import { ILoaderProps, Loader } from "@fluidframework/container-loader"; -import type { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import type { IRequest, IResponse } from "@fluidframework/core-interfaces"; -import { create404Response } from "@fluidframework/runtime-utils"; -import type { IDetachedModel, IModelLoader, ModelMakerCallback } from "./interfaces"; +import type { IRequest } from "@fluidframework/core-interfaces"; +import type { IDetachedModel, IModelLoader } from "./interfaces"; import { IModelContainerRuntimeEntryPoint } from "./modelContainerRuntimeFactory"; // This ModelLoader works on a convention, that the container it will load a model for must respond to a specific @@ -20,41 +18,6 @@ import { IModelContainerRuntimeEntryPoint } from "./modelContainerRuntimeFactory // that contract -- the container author provides a ModelMakerCallback that will produce the model given a container // runtime and container, and this helper will appropriately translate to/from the request/response format. -const modelUrl = "model"; - -interface IModelRequest extends IRequest { - url: typeof modelUrl; - headers: { - containerRef: IContainer; - }; -} - -const isModelRequest = (request: IRequest): request is IModelRequest => - request.url === modelUrl && request.headers?.containerRef !== undefined; - -/** - * A helper function for container authors, which generates the request handler they need to align with the - * ModelLoader contract. - * @param modelMakerCallback - A callback that will produce the model for the container - * @returns A request handler that can be provided to the container runtime factory - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export const makeModelRequestHandler = ( - modelMakerCallback: ModelMakerCallback, -) => { - return async (request: IRequest, runtime: IContainerRuntime): Promise => { - // The model request format is for an empty path (i.e. "") and passing a reference to the container in the - // header as containerRef. - if (isModelRequest(request)) { - const container: IContainer = request.headers.containerRef; - const model = await modelMakerCallback(runtime, container); - return { status: 200, mimeType: "fluid/object", value: model }; - } - return create404Response(request); - }; -}; - /** * @internal */ diff --git a/examples/version-migration/live-schema-upgrade/package.json b/examples/version-migration/live-schema-upgrade/package.json index 115e423c64fe..49c74f15159a 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Example application that demonstrates how to add a data object to a live container.", "homepage": "https://fluidframework.com", diff --git a/examples/version-migration/same-container/package.json b/examples/version-migration/same-container/package.json index d73e276b42e5..f1d44eb709ce 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Using external data to initialize the container state and serialize out afterwards.", "homepage": "https://fluidframework.com", diff --git a/examples/version-migration/schema-upgrade/package.json b/examples/version-migration/schema-upgrade/package.json index ac426d2453c4..7d1c36e8ead9 100644 --- a/examples/version-migration/schema-upgrade/package.json +++ b/examples/version-migration/schema-upgrade/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/app-integration-schema-upgrade", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Using external data to initialize the container state and serialize out afterwards.", "homepage": "https://fluidframework.com", diff --git a/examples/version-migration/tree-shim/package.json b/examples/version-migration/tree-shim/package.json index 713b7ed63cfd..c643f7b620b3 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Migrating from legacy SharedTree to new SharedTree using a tree shim.", "homepage": "https://fluidframework.com", diff --git a/examples/view-integration/container-views/package.json b/examples/view-integration/container-views/package.json index be6a4f268b19..6d5de0c5f3be 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid Container & data store sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", diff --git a/examples/view-integration/external-views/package.json b/examples/view-integration/external-views/package.json index d587eb100461..9772157e9b8f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Minimal Fluid Container & Data Object sample to implement a collaborative dice roller as a standalone app.", "homepage": "https://fluidframework.com", diff --git a/examples/view-integration/view-framework-sampler/package.json b/examples/view-integration/view-framework-sampler/package.json index a1b543ff0fbd..8edc1c4e73e3 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Example of integrating a Fluid data object with a variety of view frameworks.", "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/examples/partial-checkout/package.json b/experimental/PropertyDDS/examples/partial-checkout/package.json index 50a196cce0b2..44eeb863f93d 100644 --- a/experimental/PropertyDDS/examples/partial-checkout/package.json +++ b/experimental/PropertyDDS/examples/partial-checkout/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/partial-checkout", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "A sample using property-dds and property-binder to create a reactive application.", "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/examples/property-inspector/package.json b/experimental/PropertyDDS/examples/property-inspector/package.json index 74700164513a..46c77205cd30 100644 --- a/experimental/PropertyDDS/examples/property-inspector/package.json +++ b/experimental/PropertyDDS/examples/property-inspector/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/property-inspector", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "TreeTable representation for property-dds which allow browsing, editing and searching.", "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/examples/schemas/package.json b/experimental/PropertyDDS/examples/schemas/package.json index be47a60466ae..d92b7f4d0f7c 100644 --- a/experimental/PropertyDDS/examples/schemas/package.json +++ b/experimental/PropertyDDS/examples/schemas/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/schemas", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Centralized package for storing schemas used by the samples.", "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/packages/property-binder/package.json b/experimental/PropertyDDS/packages/property-binder/package.json index 9bb90b6980cc..d03deed9abef 100644 --- a/experimental/PropertyDDS/packages/property-binder/package.json +++ b/experimental/PropertyDDS/packages/property-binder/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-binder", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Data Binder for Fluid PropertyDDS", "keywords": [], "homepage": "https://fluidframework.com", diff --git a/experimental/PropertyDDS/packages/property-changeset/package.json b/experimental/PropertyDDS/packages/property-changeset/package.json index 2d717f6f365a..b3eebe9e8ed5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "property changeset definitions and related functionalities", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-common/package.json b/experimental/PropertyDDS/packages/property-common/package.json index 36b335d65413..580df86ba117 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "common functions used in properties", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json b/experimental/PropertyDDS/packages/property-common/platform-dependent/package.json index 1b524a61dba1..9dc427b631bd 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.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/package.json b/experimental/PropertyDDS/packages/property-dds/package.json index dd86b4407b90..8b3151464bf2 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "definition of the property distributed data store", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-inspector-table/package.json b/experimental/PropertyDDS/packages/property-inspector-table/package.json index 33093a9d8160..25923cc2112c 100644 --- a/experimental/PropertyDDS/packages/property-inspector-table/package.json +++ b/experimental/PropertyDDS/packages/property-inspector-table/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-inspector-table", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Property Inspector Table component", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-properties/package.json b/experimental/PropertyDDS/packages/property-properties/package.json index 14262a4563d5..4a91959731c4 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "definitions of properties", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-proxy/package.json b/experimental/PropertyDDS/packages/property-proxy/package.json index e244b27f0bac..7f27903db8f8 100644 --- a/experimental/PropertyDDS/packages/property-proxy/package.json +++ b/experimental/PropertyDDS/packages/property-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-proxy", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Proxify PropertyTree to interact with them in a JavaScript like manner", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-query/package.json b/experimental/PropertyDDS/packages/property-query/package.json index 6a7c97ca8d73..c2bc277d3f5f 100644 --- a/experimental/PropertyDDS/packages/property-query/package.json +++ b/experimental/PropertyDDS/packages/property-query/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-query", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "QueryService implementation", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/packages/property-shared-tree-interop/package.json b/experimental/PropertyDDS/packages/property-shared-tree-interop/package.json index 53cb378b4916..679561ec5a25 100644 --- a/experimental/PropertyDDS/packages/property-shared-tree-interop/package.json +++ b/experimental/PropertyDDS/packages/property-shared-tree-interop/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-shared-tree-interop", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Utilities for migration from PropertyDDS to the new SharedTree DDS", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/PropertyDDS/services/property-query-service/package.json b/experimental/PropertyDDS/services/property-query-service/package.json index 2621acb9835b..35371a95d445 100644 --- a/experimental/PropertyDDS/services/property-query-service/package.json +++ b/experimental/PropertyDDS/services/property-query-service/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/property-query-service", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Moira service", "homepage": "https://fluidframework.com", diff --git a/experimental/dds/attributable-map/package.json b/experimental/dds/attributable-map/package.json index fb419e90a060..99258e55adda 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed map", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/attributable-map/src/packageVersion.ts b/experimental/dds/attributable-map/src/packageVersion.ts index ac0de0199b80..049acec820c5 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/ot/ot/package.json b/experimental/dds/ot/ot/package.json index 3b74d857bae2..558f6209a1c9 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed data structure for hosting ottypes", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/ot/ot/src/packageVersion.ts b/experimental/dds/ot/ot/src/packageVersion.ts index 868f88b446a0..dd4ad3253484 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/ot/sharejs/json1/api-report/sharejs-json1.api.md b/experimental/dds/ot/sharejs/json1/api-report/sharejs-json1.api.md index dea4fa7781c5..47efa8bff1c6 100644 --- a/experimental/dds/ot/sharejs/json1/api-report/sharejs-json1.api.md +++ b/experimental/dds/ot/sharejs/json1/api-report/sharejs-json1.api.md @@ -45,13 +45,13 @@ export class SharedJson1 extends SharedOT { // (undocumented) static getFactory(): Json1Factory; // (undocumented) - insert(path: Path, value: Serializable): void; + insert(path: Path, value: Serializable): void; // (undocumented) move(from: Path, to: Path): void; // (undocumented) remove(path: Path, value?: boolean): void; // (undocumented) - replace(path: Path, oldValue: Serializable, newValue: Serializable): void; + replace(path: Path, oldValue: Serializable, newValue: Serializable): void; // (undocumented) protected transform(input: JSONOp, transform: JSONOp): JSONOp; } diff --git a/experimental/dds/ot/sharejs/json1/package.json b/experimental/dds/ot/sharejs/json1/package.json index 138fa3dc2368..4c45d4c3b150 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed data structure for hosting ottypes", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/ot/sharejs/json1/src/json1.ts b/experimental/dds/ot/sharejs/json1/src/json1.ts index b51239b9bfa0..257bcad55080 100644 --- a/experimental/dds/ot/sharejs/json1/src/json1.ts +++ b/experimental/dds/ot/sharejs/json1/src/json1.ts @@ -54,7 +54,7 @@ export class SharedJson1 extends SharedOT { return Json1OTType.apply(state, op) as Doc; } - public insert(path: Path, value: Serializable) { + public insert(path: Path, value: Serializable) { this.apply(insertOp(path, value as Doc)); } @@ -66,7 +66,7 @@ export class SharedJson1 extends SharedOT { this.apply(removeOp(path, value)); } - public replace(path: Path, oldValue: Serializable, newValue: Serializable) { + public replace(path: Path, oldValue: Serializable, newValue: Serializable) { this.apply(replaceOp(path, oldValue as Doc, newValue as Doc)); } } diff --git a/experimental/dds/ot/sharejs/json1/src/packageVersion.ts b/experimental/dds/ot/sharejs/json1/src/packageVersion.ts index 80487b487cc7..083a37b1d937 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/ot/sharejs/json1/src/test/json1.spec.ts b/experimental/dds/ot/sharejs/json1/src/test/json1.spec.ts index 06cd02d97456..da638114037d 100644 --- a/experimental/dds/ot/sharejs/json1/src/test/json1.spec.ts +++ b/experimental/dds/ot/sharejs/json1/src/test/json1.spec.ts @@ -31,6 +31,11 @@ function createConnectedOT(id: string, runtimeFactory: MockContainerRuntimeFacto return ot; } +interface ITestObject { + x: number; + y: number; +} + describe("SharedJson1", () => { describe("Local state", () => { let ot: SharedJson1; @@ -40,7 +45,7 @@ describe("SharedJson1", () => { ot.replace([], null, {}); }); - const expect = (expected: Jsonable) => { + const expect = (expected: Jsonable) => { assert.deepEqual(ot.get(), expected); }; @@ -67,6 +72,12 @@ describe("SharedJson1", () => { ot.insert(["x", 0], 1); expect({ x: [1] }); }); + + it("object", () => { + const obj: ITestObject = { x: 1, y: 2 }; + ot.insert(["o"], obj); + expect({ o: { x: 1, y: 2 } }); + }); }); describe("remove()", () => { @@ -109,7 +120,7 @@ describe("SharedJson1", () => { expect([]); }); - const expect = (expected?: Jsonable) => { + const expect = (expected?: Jsonable) => { containerRuntimeFactory.processAllMessages(); const actual1 = doc1.get(); diff --git a/experimental/dds/sequence-deprecated/api-report/sequence-deprecated.api.md b/experimental/dds/sequence-deprecated/api-report/sequence-deprecated.api.md index 5648e505795a..31848d40ddba 100644 --- a/experimental/dds/sequence-deprecated/api-report/sequence-deprecated.api.md +++ b/experimental/dds/sequence-deprecated/api-report/sequence-deprecated.api.md @@ -224,7 +224,7 @@ export class SparseMatrixFactory implements IChannelFactory { } // @internal @deprecated (undocumented) -export type SparseMatrixItem = Serializable; +export type SparseMatrixItem = any; export { SubSequence } diff --git a/experimental/dds/sequence-deprecated/package.json b/experimental/dds/sequence-deprecated/package.json index b332e04ac3cb..742639986440 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Deprecated distributed sequences", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/sequence-deprecated/src/packageVersion.ts b/experimental/dds/sequence-deprecated/src/packageVersion.ts index 3d14ced12b8e..a33329eaef36 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/sequence-deprecated/src/sparsematrix.ts b/experimental/dds/sequence-deprecated/src/sparsematrix.ts index 4dd562d1c3f9..6da83a49920c 100644 --- a/experimental/dds/sequence-deprecated/src/sparsematrix.ts +++ b/experimental/dds/sequence-deprecated/src/sparsematrix.ts @@ -11,7 +11,6 @@ import { IFluidDataStoreRuntime, IChannelServices, IChannelFactory, - Serializable, Jsonable, } from "@fluidframework/datastore-definitions"; import { ISharedObject } from "@fluidframework/shared-object-base"; @@ -92,7 +91,7 @@ export class PaddingSegment extends BaseSegment { * Use {@link @fluidframework/matrix#SharedMatrix} instead. * @internal */ -export type SparseMatrixItem = Serializable; +export type SparseMatrixItem = any; /** * @deprecated `RunSegment` is part of an abandoned prototype. diff --git a/experimental/dds/tree/api-report/experimental-tree.api.md b/experimental/dds/tree/api-report/experimental-tree.api.md index dc65dbdb0aed..34246be500c0 100644 --- a/experimental/dds/tree/api-report/experimental-tree.api.md +++ b/experimental/dds/tree/api-report/experimental-tree.api.md @@ -26,7 +26,6 @@ import { ITelemetryContext } from '@fluidframework/runtime-definitions'; import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; import { ITelemetryProperties } from '@fluidframework/core-interfaces'; import { ITree } from '@fluid-experimental/tree2'; -import type { Serializable } from '@fluidframework/datastore-definitions'; import { SharedObject } from '@fluidframework/shared-object-base'; import { TreeFactory } from '@fluid-experimental/tree2'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; @@ -731,7 +730,7 @@ export interface ParentData { } // @internal -export type Payload = Serializable; +export type Payload = any; // @internal export function placeFromStablePlace(view: TreeView, stablePlace: StablePlace): TreeViewPlace; diff --git a/experimental/dds/tree/package.json b/experimental/dds/tree/package.json index 78e21cc777fa..4b96eb59732d 100644 --- a/experimental/dds/tree/package.json +++ b/experimental/dds/tree/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/tree", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed tree", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/tree/src/migration-shim/packageVersion.ts b/experimental/dds/tree/src/migration-shim/packageVersion.ts index a45aebd1f9a9..ffb166ac028e 100644 --- a/experimental/dds/tree/src/migration-shim/packageVersion.ts +++ b/experimental/dds/tree/src/migration-shim/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = '@fluid-experimental/migration-shim'; -export const pkgVersion = '2.0.0-internal.7.4.0'; +export const pkgVersion = '2.0.0-internal.8.0.0'; diff --git a/experimental/dds/tree/src/persisted-types/0.0.2.ts b/experimental/dds/tree/src/persisted-types/0.0.2.ts index a11eb03aebab..a4fb1ae4a0fd 100644 --- a/experimental/dds/tree/src/persisted-types/0.0.2.ts +++ b/experimental/dds/tree/src/persisted-types/0.0.2.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import type { Serializable } from '@fluidframework/datastore-definitions'; import type { EditId, Definition, @@ -94,7 +93,7 @@ export interface EditBase { * TODO:#51984: Allow opting into heuristic blobbing in snapshots with a special IFluid key. * @internal */ -export type Payload = Serializable; +export type Payload = any; /** * Json compatible map as object. diff --git a/experimental/dds/tree/src/test/utilities/TestUtilities.ts b/experimental/dds/tree/src/test/utilities/TestUtilities.ts index b455f1afd0a4..43ca8a57eeaa 100644 --- a/experimental/dds/tree/src/test/utilities/TestUtilities.ts +++ b/experimental/dds/tree/src/test/utilities/TestUtilities.ts @@ -27,9 +27,8 @@ import { import type { IContainer, IHostLoader } from '@fluidframework/container-definitions'; import type { IFluidCodeDetails, IFluidHandle, IRequestHeader } from '@fluidframework/core-interfaces'; import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions'; -import { IContainerRuntimeBase } from '@fluidframework/runtime-definitions'; import { ConfigTypes, IConfigProviderBase, createChildLogger } from '@fluidframework/telemetry-utils'; -import { ITelemetryBaseLogger, IRequest } from '@fluidframework/core-interfaces'; +import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; import { AttributionId, DetachedSequenceId, @@ -324,28 +323,21 @@ export async function setUpLocalServerTestSharedTree( factory = SharedTree.getFactory(writeFormat ?? WriteFormat.v0_1_1, options); } const registry: ChannelFactoryRegistry = [[treeId, factory]]; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = () => - new TestContainerRuntimeFactory( - TestDataStoreType, - new TestFluidObjectFactory(registry), - { - summaryOptions: { - summaryConfigOverrides: { - ...DefaultSummaryConfiguration, - ...{ - minIdleTime: 1000, // Manually set idle times so some SharedTree tests don't timeout. - maxIdleTime: 1000, - maxTime: 1000 * 12, - initialSummarizerDelayMs: 0, - }, + new TestContainerRuntimeFactory(TestDataStoreType, new TestFluidObjectFactory(registry), { + summaryOptions: { + summaryConfigOverrides: { + ...DefaultSummaryConfiguration, + ...{ + minIdleTime: 1000, // Manually set idle times so some SharedTree tests don't timeout. + maxIdleTime: 1000, + maxTime: 1000 * 12, + initialSummarizerDelayMs: 0, }, }, }, - [innerRequestHandler] - ); + }); const defaultCodeDetails: IFluidCodeDetails = { package: 'defaultTestPackage', diff --git a/experimental/dds/tree2/package.json b/experimental/dds/tree2/package.json index 36f235f9fc53..f397751677b7 100644 --- a/experimental/dds/tree2/package.json +++ b/experimental/dds/tree2/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/tree2", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Tree", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/tree2/src/test/domains/json/benchmarks.ts b/experimental/dds/tree2/src/test/domains/json/benchmarks.ts index d01f8dd86252..d7d46330c28e 100644 --- a/experimental/dds/tree2/src/test/domains/json/benchmarks.ts +++ b/experimental/dds/tree2/src/test/domains/json/benchmarks.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import { Jsonable } from "@fluidframework/datastore-definitions"; import { forEachNode, forEachField, ITreeCursor } from "../../../core"; export function sum(cursor: ITreeCursor): number { @@ -38,7 +37,7 @@ export function sumMap(cursor: ITreeCursor): number { return total; } -export function sumDirect(jsonObj: Jsonable): number { +export function sumDirect(jsonObj: any): number { let total = 0; for (const value of Object.values(jsonObj)) { if (typeof value === "object" && value !== null) { diff --git a/experimental/framework/data-objects/package.json b/experimental/framework/data-objects/package.json index 348b70d70c07..ea383eb7b01c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A collection of ready to use Fluid Data Objects", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/framework/data-objects/src/signaler/signaler.ts b/experimental/framework/data-objects/src/signaler/signaler.ts index 9a7850f15e7a..248768b7696f 100644 --- a/experimental/framework/data-objects/src/signaler/signaler.ts +++ b/experimental/framework/data-objects/src/signaler/signaler.ts @@ -18,7 +18,7 @@ import { IErrorEvent } from "@fluidframework/core-interfaces"; /** * @internal */ -export type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void; +export type SignalListener = (clientId: string, local: boolean, payload: Jsonable) => void; /** * ISignaler defines an interface for working with signals that is similar to the more common @@ -34,7 +34,7 @@ export interface ISignaler { * @param listener - The callback signal handler to add * @returns This ISignaler */ - onSignal(signalName: string, listener: SignalListener): ISignaler; + onSignal(signalName: string, listener: SignalListener): ISignaler; /** * Remove a listener for the specified signal. It behaves in the same way as EventEmitter's * `off` method regarding multiple registrations, removal order, etc. @@ -42,13 +42,13 @@ export interface ISignaler { * @param listener - The callback signal handler to remove * @returns This ISignaler */ - offSignal(signalName: string, listener: SignalListener): ISignaler; + offSignal(signalName: string, listener: SignalListener): ISignaler; /** * Send a signal with payload to its connected listeners. * @param signalName - The name of the signal * @param payload - The data to send with the signal */ - submitSignal(signalName: string, payload?: Jsonable); + submitSignal(signalName: string, payload?: Jsonable); } /** @@ -59,7 +59,7 @@ export interface ISignaler { export interface IRuntimeSignaler { connected: boolean; on(event: "signal", listener: (message: IInboundSignalMessage, local: boolean) => void); - submitSignal(type: string, content: any): void; + submitSignal(type: string, content: Jsonable): void; } /** @@ -110,19 +110,19 @@ class InternalSignaler extends TypedEventEmitter implements ISignal // ISignaler methods - public onSignal(signalName: string, listener: SignalListener): ISignaler { + public onSignal(signalName: string, listener: SignalListener): ISignaler { const signalerSignalName = this.getSignalerSignalName(signalName); this.emitter.on(signalerSignalName, listener); return this; } - public offSignal(signalName: string, listener: SignalListener): ISignaler { + public offSignal(signalName: string, listener: SignalListener): ISignaler { const signalerSignalName = this.getSignalerSignalName(signalName); this.emitter.off(signalerSignalName, listener); return this; } - public submitSignal(signalName: string, payload?: Jsonable) { + public submitSignal(signalName: string, payload?: Jsonable) { const signalerSignalName = this.getSignalerSignalName(signalName); if (this.signaler.connected) { this.signaler.submitSignal(signalerSignalName, payload); @@ -158,17 +158,17 @@ export class Signaler // ISignaler methods Note these are all passthroughs - public onSignal(signalName: string, listener: SignalListener): ISignaler { + public onSignal(signalName: string, listener: SignalListener): ISignaler { this.signaler.onSignal(signalName, listener); return this; } - public offSignal(signalName: string, listener: SignalListener): ISignaler { + public offSignal(signalName: string, listener: SignalListener): ISignaler { this.signaler.offSignal(signalName, listener); return this; } - public submitSignal(signalName: string, payload?: Jsonable) { + public submitSignal(signalName: string, payload?: Jsonable) { this.signaler.submitSignal(signalName, payload); } } diff --git a/experimental/framework/last-edited/package.json b/experimental/framework/last-edited/package.json index 7e46d81476d6..f2ff2b080efa 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Tracks the last edited information in the Container.", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/framework/react-inputs/package.json b/experimental/framework/react-inputs/package.json index 928bc5f8f7da..7b76b7063840 100644 --- a/experimental/framework/react-inputs/package.json +++ b/experimental/framework/react-inputs/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/react-inputs", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "React support for the Aqueduct framework.", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/framework/tree-react-api/package.json b/experimental/framework/tree-react-api/package.json index 2ebfdb5a0280..83e9de4b131a 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Experimental SharedTree API for React integration.", "homepage": "https://fluidframework.com", "repository": { diff --git a/feeds/internal-build.txt b/feeds/internal-build.txt index 5c27d7146baa..a9bc3c90e550 100644 --- a/feeds/internal-build.txt +++ b/feeds/internal-build.txt @@ -19,7 +19,6 @@ @fluidframework/datastore @fluidframework/container-runtime-definitions @fluidframework/container-runtime -@fluidframework/location-redirection-utils @fluidframework/driver-utils @fluidframework/container-loader @fluidframework/view-interfaces diff --git a/feeds/internal-test.txt b/feeds/internal-test.txt index b4d1042a6b04..98c5a76bf316 100644 --- a/feeds/internal-test.txt +++ b/feeds/internal-test.txt @@ -25,7 +25,6 @@ @fluidframework/container-runtime-definitions @fluidframework/container-runtime @fluid-private/test-loader-utils -@fluidframework/location-redirection-utils @fluidframework/driver-utils @fluidframework/container-loader @fluidframework/view-interfaces diff --git a/feeds/public.txt b/feeds/public.txt index 49a420c39bef..495e7ee7079e 100644 --- a/feeds/public.txt +++ b/feeds/public.txt @@ -17,7 +17,6 @@ @fluidframework/datastore @fluidframework/container-runtime-definitions @fluidframework/container-runtime -@fluidframework/location-redirection-utils @fluidframework/driver-utils @fluidframework/container-loader @fluidframework/view-interfaces diff --git a/lerna.json b/lerna.json index 653a1cb17a47..87b9c2f0f146 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "npmClient": "pnpm", "useWorkspaces": true } diff --git a/package.json b/package.json index fa6142e4a7bf..7f39fdabb017 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "client-release-group-root", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/common/client-utils/package.json b/packages/common/client-utils/package.json index 1b2bba579c27..d14afd2ec6d3 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Not intended for use outside the Fluid Framework.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/common/container-definitions/api-report/container-definitions.api.md b/packages/common/container-definitions/api-report/container-definitions.api.md index e339bf256e3f..7d86f7e98ba0 100644 --- a/packages/common/container-definitions/api-report/container-definitions.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.api.md @@ -17,12 +17,10 @@ import { IErrorBase } from '@fluidframework/core-interfaces'; import { IErrorEvent } from '@fluidframework/core-interfaces'; import { IEvent } from '@fluidframework/core-interfaces'; import { IEventProvider } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IGenericError } from '@fluidframework/core-interfaces'; import { IQuorumClients } from '@fluidframework/protocol-definitions'; import { IRequest } from '@fluidframework/core-interfaces'; import { IResolvedUrl } from '@fluidframework/driver-definitions'; -import { IResponse } from '@fluidframework/core-interfaces'; import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions'; import { ISequencedProposal } from '@fluidframework/protocol-definitions'; import { ISignalMessage } from '@fluidframework/protocol-definitions'; @@ -124,7 +122,7 @@ export interface IConnectionDetails { } // @alpha -export interface IContainer extends IEventProvider, IFluidRouter { +export interface IContainer extends IEventProvider { attach(request: IRequest, attachProps?: { deltaConnection?: "none" | "delayed"; }): Promise; @@ -141,22 +139,13 @@ export interface IContainer extends IEventProvider, IFluidRout readonly disposed?: boolean; forceReadonly?(readonly: boolean): any; getAbsoluteUrl(relativeUrl: string): Promise; - getEntryPoint(): Promise; + getEntryPoint(): Promise; getLoadedCodeDetails(): IFluidCodeDetails | undefined; getQuorum(): IQuorumClients; getSpecifiedCodeDetails(): IFluidCodeDetails | undefined; - // @deprecated (undocumented) - readonly IFluidRouter: IFluidRouter; readonly isDirty: boolean; proposeCodeDetails(codeDetails: IFluidCodeDetails): Promise; readonly readOnlyInfo: ReadOnlyInfo; - // @deprecated (undocumented) - request(request: { - url: "/"; - headers?: undefined; - }): Promise; - // @deprecated - request(request: IRequest): Promise; resolvedUrl: IResolvedUrl | undefined; serialize(): string; } @@ -402,10 +391,6 @@ export interface IHostLoader extends ILoader { // @alpha export interface ILoader extends Partial { - // @deprecated (undocumented) - readonly IFluidRouter: IFluidRouter; - // @deprecated (undocumented) - request(request: IRequest): Promise; resolve(request: IRequest, pendingLocalState?: string): Promise; } @@ -433,14 +418,6 @@ export type ILoaderOptions = { maxClientLeaveWaitTime?: number; }; -// @internal @deprecated (undocumented) -export interface IPendingLocalState { - // (undocumented) - pendingRuntimeState: unknown; - // (undocumented) - url: string; -} - // @alpha (undocumented) export interface IProvideFluidCodeDetailsComparer { // (undocumented) @@ -468,15 +445,11 @@ export interface IResolvedFluidCodeDetails extends IFluidCodeDetails { // @alpha export interface IRuntime extends IDisposable { createSummary(blobRedirectTable?: Map): ISummaryTree; - getEntryPoint(): Promise; + getEntryPoint(): Promise; getPendingLocalState(props?: IGetPendingLocalStateProps): unknown; - // @deprecated - notifyAttaching(snapshot: ISnapshotTreeWithBlobContents): void; notifyOpReplay?(message: ISequencedDocumentMessage): Promise; process(message: ISequencedDocumentMessage, local: boolean): any; processSignal(message: any, local: boolean): any; - // @deprecated - request(request: IRequest): Promise; setAttachState(attachState: AttachState.Attaching | AttachState.Attached): void; setConnectionState(connected: boolean, clientId?: string): any; } @@ -501,7 +474,7 @@ export const isFluidPackage: (pkg: unknown) => pkg is Readonly; // @alpha export interface ISnapshotTreeWithBlobContents extends ISnapshotTree { // (undocumented) - blobsContents: { + blobsContents?: { [path: string]: ArrayBufferLike; }; // (undocumented) diff --git a/packages/common/container-definitions/package.json b/packages/common/container-definitions/package.json index 15f6cae9beed..02ab59a13468 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container definitions", "homepage": "https://fluidframework.com", "repository": { @@ -78,6 +78,32 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedInterfaceDeclaration_IPendingLocalState": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_IRuntime": { + "backCompat": false + }, + "InterfaceDeclaration_ISnapshotTreeWithBlobContents": { + "backCompat": false + }, + "InterfaceDeclaration_IContainer": { + "backCompat": false + }, + "InterfaceDeclaration_IContainerContext": { + "backCompat": false + }, + "InterfaceDeclaration_IHostLoader": { + "backCompat": false + }, + "InterfaceDeclaration_ILoader": { + "backCompat": false + }, + "InterfaceDeclaration_IProvideLoader": { + "backCompat": false + } + } } } diff --git a/packages/common/container-definitions/src/index.ts b/packages/common/container-definitions/src/index.ts index f6c22ebffaa5..3fab1cba931d 100644 --- a/packages/common/container-definitions/src/index.ts +++ b/packages/common/container-definitions/src/index.ts @@ -42,7 +42,6 @@ export { ILoader, ILoaderHeader, ILoaderOptions, - IPendingLocalState, IProvideLoader, IResolvedFluidCodeDetails, ISnapshotTreeWithBlobContents, diff --git a/packages/common/container-definitions/src/loader.ts b/packages/common/container-definitions/src/loader.ts index d3cecd179129..31fffabe9731 100644 --- a/packages/common/container-definitions/src/loader.ts +++ b/packages/common/container-definitions/src/loader.ts @@ -3,15 +3,7 @@ * Licensed under the MIT License. */ -import { - IRequest, - IResponse, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, - FluidObject, - IEvent, - IEventProvider, -} from "@fluidframework/core-interfaces"; +import { IRequest, FluidObject, IEvent, IEventProvider } from "@fluidframework/core-interfaces"; import { IClientDetails, IDocumentMessage, @@ -305,7 +297,7 @@ export type ConnectionState = * @alpha */ // eslint-disable-next-line import/no-deprecated -export interface IContainer extends IEventProvider, IFluidRouter { +export interface IContainer extends IEventProvider { /** * The Delta Manager supporting the op stream for this Container */ @@ -407,40 +399,6 @@ export interface IContainer extends IEventProvider, IFluidRout */ getAbsoluteUrl(relativeUrl: string): Promise; - /** - * @deprecated Requesting will not be supported in a future major release. - * Instead, access the objects in a Fluid Container using entryPoint, and then navigate from there using - * app-specific logic (e.g. retrieving handles from the entryPoint's DDSes, or a container's entryPoint object - * could implement a request paradigm itself) - * - * IMPORTANT: This overload is provided for back-compat where IContainer.request(\{ url: "/" \}) is already implemented and used. - * The functionality it can provide (if the Container implementation is built for it) is redundant with @see {@link IContainer.getEntryPoint}. - * - * Refer to Removing-IFluidRouter.md for details on migrating from the request pattern to using entryPoint. - * - * @param request - Only requesting \{ url: "/" \} is supported, requesting arbitrary URLs is deprecated. - */ - request(request: { url: "/"; headers?: undefined }): Promise; - - /** - * Issue a request against the container for a resource. - * @param request - The request to be issued against the container - * - * @deprecated Requesting an arbitrary URL with headers will not be supported in a future major release. - * Instead, access the objects in a Fluid Container using entryPoint, and then navigate from there using - * app-specific logic (e.g. retrieving handles from the entryPoint's DDSes, or a container's entryPoint object - * could implement a request paradigm itself) - * - * Refer to Removing-IFluidRouter.md for details on migrating from the request pattern to using entryPoint. - */ - request(request: IRequest): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - readonly IFluidRouter: IFluidRouter; - /** * Provides the current state of the container's connection to the ordering service. * @@ -508,7 +466,7 @@ export interface IContainer extends IEventProvider, IFluidRout * Exposes the entryPoint for the container. * Use this as the primary way of getting access to the user-defined logic within the container. */ - getEntryPoint(): Promise; + getEntryPoint(): Promise; } /** @@ -526,17 +484,6 @@ export interface ILoader extends Partial { * a request against the server found from the resolve step. */ resolve(request: IRequest, pendingLocalState?: string): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request. - */ - request(request: IRequest): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request. - */ - // eslint-disable-next-line import/no-deprecated - readonly IFluidRouter: IFluidRouter; } /** @@ -716,17 +663,6 @@ export interface IProvideLoader { readonly ILoader: ILoader; } -/** - * @deprecated 0.48, This API will be removed in 0.50 - * No replacement since it is not expected anyone will depend on this outside container-loader - * See {@link https://github.com/microsoft/FluidFramework/issues/9711} for context. - * @internal - */ -export interface IPendingLocalState { - url: string; - pendingRuntimeState: unknown; -} - /** * This is used when we rehydrate a container from the snapshot. Here we put the blob contents * in separate property: {@link ISnapshotTreeWithBlobContents.blobsContents}. @@ -735,6 +671,6 @@ export interface IPendingLocalState { * @alpha */ export interface ISnapshotTreeWithBlobContents extends ISnapshotTree { - blobsContents: { [path: string]: ArrayBufferLike }; + blobsContents?: { [path: string]: ArrayBufferLike }; trees: { [path: string]: ISnapshotTreeWithBlobContents }; } diff --git a/packages/common/container-definitions/src/runtime.ts b/packages/common/container-definitions/src/runtime.ts index 2927a244fd4a..b36c245d9843 100644 --- a/packages/common/container-definitions/src/runtime.ts +++ b/packages/common/container-definitions/src/runtime.ts @@ -3,13 +3,7 @@ * Licensed under the MIT License. */ -import { - ITelemetryBaseLogger, - IDisposable, - FluidObject, - IRequest, - IResponse, -} from "@fluidframework/core-interfaces"; +import { ITelemetryBaseLogger, IDisposable, FluidObject } from "@fluidframework/core-interfaces"; import { IDocumentStorageService } from "@fluidframework/driver-definitions"; import { @@ -26,7 +20,7 @@ import { import { IAudience } from "./audience"; import { IDeltaManager } from "./deltas"; import { ICriticalContainerError } from "./error"; -import { ILoader, ILoaderOptions, ISnapshotTreeWithBlobContents } from "./loader"; +import { ILoader, ILoaderOptions } from "./loader"; import { IFluidCodeDetails } from "./fluidPackage"; /** @@ -59,12 +53,6 @@ export enum AttachState { * @alpha */ export interface IRuntime extends IDisposable { - /** - * Executes a request against the runtime - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - request(request: IRequest): Promise; - /** * Notifies the runtime of a change in the connection state */ @@ -102,13 +90,6 @@ export interface IRuntime extends IDisposable { */ getPendingLocalState(props?: IGetPendingLocalStateProps): unknown; - /** - * Notify runtime that container is moving to "Attaching" state - * @param snapshot - snapshot created at attach time - * @deprecated not necessary after op replay moved to Container - */ - notifyAttaching(snapshot: ISnapshotTreeWithBlobContents): void; - /** * Notify runtime that we have processed a saved message, so that it can do async work (applying * stashed ops) after having processed it. @@ -121,7 +102,7 @@ export interface IRuntime extends IDisposable { * * @see {@link IContainer.getEntryPoint} */ - getEntryPoint(): Promise; + getEntryPoint(): Promise; } /** diff --git a/packages/common/container-definitions/src/test/types/requestSignatureDeprecation.ts b/packages/common/container-definitions/src/test/types/requestSignatureDeprecation.ts deleted file mode 100644 index a8846a473e89..000000000000 --- a/packages/common/container-definitions/src/test/types/requestSignatureDeprecation.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint deprecation/deprecation: "error" */ - -import { IContainer } from "../../loader"; - -declare const container: IContainer; - -// These are deprecated -// eslint-disable-next-line deprecation/deprecation -container.request({ url: "/" }); -// eslint-disable-next-line deprecation/deprecation -container.request({ url: "/", headers: { shouldBeDeprecated: true } }); -// eslint-disable-next-line deprecation/deprecation -container.request({ url: "/should/be/deprecated" }); 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 9bad7bb2cd8e..b9ad3814e657 100644 --- a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts +++ b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts @@ -403,6 +403,7 @@ declare function get_current_InterfaceDeclaration_IContainer(): declare function use_old_InterfaceDeclaration_IContainer( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainer( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainer()); /* @@ -427,6 +428,7 @@ declare function get_current_InterfaceDeclaration_IContainerContext(): declare function use_old_InterfaceDeclaration_IContainerContext( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainerContext( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainerContext()); /* @@ -979,6 +981,7 @@ declare function get_current_InterfaceDeclaration_IHostLoader(): declare function use_old_InterfaceDeclaration_IHostLoader( use: TypeOnly): void; use_old_InterfaceDeclaration_IHostLoader( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IHostLoader()); /* @@ -1003,6 +1006,7 @@ declare function get_current_InterfaceDeclaration_ILoader(): declare function use_old_InterfaceDeclaration_ILoader( use: TypeOnly): void; use_old_InterfaceDeclaration_ILoader( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ILoader()); /* @@ -1056,26 +1060,14 @@ use_old_TypeAliasDeclaration_ILoaderOptions( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IPendingLocalState": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IPendingLocalState": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IPendingLocalState(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IPendingLocalState( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IPendingLocalState( - get_old_InterfaceDeclaration_IPendingLocalState()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IPendingLocalState": {"backCompat": false} +* "RemovedInterfaceDeclaration_IPendingLocalState": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IPendingLocalState(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IPendingLocalState( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IPendingLocalState( - get_current_InterfaceDeclaration_IPendingLocalState()); /* * Validate forward compat by using old type in place of current type @@ -1123,6 +1115,7 @@ declare function get_current_InterfaceDeclaration_IProvideLoader(): declare function use_old_InterfaceDeclaration_IProvideLoader( use: TypeOnly): void; use_old_InterfaceDeclaration_IProvideLoader( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IProvideLoader()); /* @@ -1195,6 +1188,7 @@ declare function get_current_InterfaceDeclaration_IRuntime(): declare function use_old_InterfaceDeclaration_IRuntime( use: TypeOnly): void; use_old_InterfaceDeclaration_IRuntime( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IRuntime()); /* @@ -1267,6 +1261,7 @@ declare function get_current_InterfaceDeclaration_ISnapshotTreeWithBlobContents( declare function use_old_InterfaceDeclaration_ISnapshotTreeWithBlobContents( use: TypeOnly): void; use_old_InterfaceDeclaration_ISnapshotTreeWithBlobContents( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ISnapshotTreeWithBlobContents()); /* diff --git a/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 094cfcb10fa6..7ddf49d6a8d6 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -97,23 +97,28 @@ const entryPoint = await container.getEntryPoint(); | API | Deprecated in | Removed in | | -------------------------------------------------------------------------------------------- | -------------------- | -------------------- | -| `IContainer.request` (except calling with "/") | 2.0.0-internal.6.0.0 | | -| `IDataStore.request` (except calling with "/") | 2.0.0-internal.6.0.0 | | -| `IContainer.IFluidRouter` | 2.0.0-internal.6.0.0 | | -| `IDataStore.IFluidRouter` | 2.0.0-internal.6.0.0 | | -| `request` and `IFluidRouter` on `ILoader` and `Loader` | 2.0.0-internal.6.0.0 | | -| `request` and `IFluidRouter` on `IRuntime` and `ContainerRuntime` | 2.0.0-internal.6.0.0 | | -| `request` and `IFluidRouter` on `IFluidDataStoreRuntime` and `FluidDataStoreRuntime` | 2.0.0-internal.6.0.0 | | -| `request` and `IFluidRouter` on `IFluidDataStoreChannel` | 2.0.0-internal.6.0.0 | | -| `getRootDataStore` on `IContainerRuntime` and `ContainerRuntime` | 2.0.0-internal.6.0.0 | | -| `resolveHandle` on `IContainerRuntime` | 2.0.0-internal.7.0.0 | | -| `IFluidHandleContext` on `IContainerRuntimeBase` | 2.0.0-internal.7.0.0 | | +| `IContainer.request` (except calling with "/") | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `IDataStore.request` (except calling with "/") | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `IContainer.IFluidRouter` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `IDataStore.IFluidRouter` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `request` and `IFluidRouter` on `ILoader` and `Loader` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `request` and `IFluidRouter` on `IRuntime` and `ContainerRuntime` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `request` and `IFluidRouter` on `IFluidDataStoreRuntime` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `IFluidRouter` on `IFluidDataStoreChannel` and `FluidDataStoreRuntime` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `getRootDataStore` on `IContainerRuntime` and `ContainerRuntime` | 2.0.0-internal.6.0.0 | 2.0.0-internal.8.0.0 | +| `resolveHandle` on `IContainerRuntime` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `IFluidHandleContext` on `IContainerRuntimeBase` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | | `requestHandler` property in `ContainerRuntime.loadRuntime(...)` | 2.0.0-internal.7.0.0 | | | `RuntimeRequestHandler` and `RuntimeRequestHandlerBuilder` | 2.0.0-internal.7.0.0 | | -| `request` and `IFluidRouter` on `IContainer` and `Container` | 2.0.0-internal.7.0.0 | | -| `request` and `IFluidRouter` on `IDataStore` | 2.0.0-internal.7.0.0 | | -| `IFluidRouter` and `IProvideFluidRouter` | 2.0.0-internal.7.0.0 | | -| `requestFluidObject` | 2.0.0-internal.7.0.0 | | -| `requestResolvedObjectFromContainer` | 2.0.0-internal.7.0.0 | | -| `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` | 2.0.0-internal.7.0.0 | | +| `request` and `IFluidRouter` on `IContainer` and `Container` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `request` on `IDataStore` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `IFluidRouter` and `IProvideFluidRouter` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `requestFluidObject` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `requestResolvedObjectFromContainer` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | +| `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | + +The removal of some items will need to wait for the LTS version of the `Loader` to reach "2.0.0-internal.7.0.0". This is because old `Loader` or `Container` code doesn't know about the new `entryPoint` pattern and will still attempt to use the `request` pattern. The following items are affected: + +- `requestHandler` property in `ContainerRuntime.loadRuntime(...)`, `BaseContainerRuntimeFactory`, `ContainerRuntimeFactoryWithDefaultDataStore`, `mixinAttributor`, `RuntimeFactory`, `TestContainerRuntimeFactory` +- `RuntimeRequestHandler` and `RuntimeRequestHandlerBuilder` diff --git a/packages/common/core-interfaces/api-report/core-interfaces.api.md b/packages/common/core-interfaces/api-report/core-interfaces.api.md index 827c3167d7db..dc77400bc98d 100644 --- a/packages/common/core-interfaces/api-report/core-interfaces.api.md +++ b/packages/common/core-interfaces/api-report/core-interfaces.api.md @@ -305,15 +305,6 @@ export interface IFluidPackageEnvironment { }; } -// @alpha @deprecated (undocumented) -export const IFluidRouter: keyof IProvideFluidRouter; - -// @alpha @deprecated (undocumented) -export interface IFluidRouter extends IProvideFluidRouter { - // (undocumented) - request(request: IRequest): Promise; -} - // @internal (undocumented) export const IFluidRunnable: keyof IProvideFluidRunnable; @@ -361,12 +352,6 @@ export interface IProvideFluidLoadable { readonly IFluidLoadable: IFluidLoadable; } -// @alpha @deprecated -export interface IProvideFluidRouter { - // (undocumented) - readonly IFluidRouter: IFluidRouter; -} - // @internal (undocumented) export interface IProvideFluidRunnable { // (undocumented) diff --git a/packages/common/core-interfaces/package.json b/packages/common/core-interfaces/package.json index 56740b1cb288..8078f818544b 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid object interfaces", "homepage": "https://fluidframework.com", "repository": { @@ -70,6 +70,19 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedVariableDeclaration_IFluidRouter": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_IFluidRouter": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_IProvideFluidRouter": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/common/core-interfaces/src/fluidRouter.ts b/packages/common/core-interfaces/src/fluidRouter.ts index c1a0b775bd50..8c3a8630d4b9 100644 --- a/packages/common/core-interfaces/src/fluidRouter.ts +++ b/packages/common/core-interfaces/src/fluidRouter.ts @@ -34,26 +34,3 @@ export interface IResponse { headers?: { [key: string]: any }; stack?: string; } - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @alpha - */ -export const IFluidRouter: keyof IProvideFluidRouter = "IFluidRouter"; - -/** - * Request routing - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @alpha - */ -export interface IProvideFluidRouter { - readonly IFluidRouter: IFluidRouter; -} - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @alpha - */ -export interface IFluidRouter extends IProvideFluidRouter { - request(request: IRequest): Promise; -} diff --git a/packages/common/core-interfaces/src/index.ts b/packages/common/core-interfaces/src/index.ts index fff409be3624..b96c89d6f99f 100644 --- a/packages/common/core-interfaces/src/index.ts +++ b/packages/common/core-interfaces/src/index.ts @@ -45,13 +45,7 @@ export { // TypeScript forgets the index signature when customers augment IRequestHeader if we export *. // So we export the explicit members as a workaround: // https://github.com/microsoft/TypeScript/issues/18877#issuecomment-476921038 -export { - IRequest, - IRequestHeader, - IResponse, - IProvideFluidRouter, - IFluidRouter, -} from "./fluidRouter"; +export { IRequest, IRequestHeader, IResponse } from "./fluidRouter"; export { IFluidHandleContext, diff --git a/packages/common/core-interfaces/src/test/types/validateCoreInterfacesPrevious.generated.ts b/packages/common/core-interfaces/src/test/types/validateCoreInterfacesPrevious.generated.ts index 0be79425c509..da645a158d00 100644 --- a/packages/common/core-interfaces/src/test/types/validateCoreInterfacesPrevious.generated.ts +++ b/packages/common/core-interfaces/src/test/types/validateCoreInterfacesPrevious.generated.ts @@ -624,50 +624,26 @@ use_old_InterfaceDeclaration_IFluidPackageEnvironment( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_IFluidRouter": {"forwardCompat": false} +* "RemovedVariableDeclaration_IFluidRouter": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_IFluidRouter(): - TypeOnly; -declare function use_current_VariableDeclaration_IFluidRouter( - use: TypeOnly): void; -use_current_VariableDeclaration_IFluidRouter( - get_old_VariableDeclaration_IFluidRouter()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_IFluidRouter": {"backCompat": false} +* "RemovedVariableDeclaration_IFluidRouter": {"backCompat": false} */ -declare function get_current_VariableDeclaration_IFluidRouter(): - TypeOnly; -declare function use_old_VariableDeclaration_IFluidRouter( - use: TypeOnly): void; -use_old_VariableDeclaration_IFluidRouter( - get_current_VariableDeclaration_IFluidRouter()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IFluidRouter": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IFluidRouter": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IFluidRouter(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IFluidRouter( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IFluidRouter( - get_old_InterfaceDeclaration_IFluidRouter()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IFluidRouter": {"backCompat": false} +* "RemovedInterfaceDeclaration_IFluidRouter": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IFluidRouter(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IFluidRouter( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IFluidRouter( - get_current_InterfaceDeclaration_IFluidRouter()); /* * Validate forward compat by using old type in place of current type @@ -864,26 +840,14 @@ use_old_InterfaceDeclaration_IProvideFluidLoadable( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IProvideFluidRouter": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IProvideFluidRouter": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IProvideFluidRouter(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IProvideFluidRouter( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IProvideFluidRouter( - get_old_InterfaceDeclaration_IProvideFluidRouter()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IProvideFluidRouter": {"backCompat": false} +* "RemovedInterfaceDeclaration_IProvideFluidRouter": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IProvideFluidRouter(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IProvideFluidRouter( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IProvideFluidRouter( - get_current_InterfaceDeclaration_IProvideFluidRouter()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/common/core-utils/api-report/core-utils.api.md b/packages/common/core-utils/api-report/core-utils.api.md index f41e3b83b11e..017ec79517da 100644 --- a/packages/common/core-utils/api-report/core-utils.api.md +++ b/packages/common/core-utils/api-report/core-utils.api.md @@ -29,8 +29,8 @@ export class Heap { // (undocumented) comp: IComparer; count(): number; - get(): T; - peek(): IHeapNode; + get(): T | undefined; + peek(): IHeapNode | undefined; remove(node: IHeapNode): void; update(node: IHeapNode): void; } diff --git a/packages/common/core-utils/package.json b/packages/common/core-utils/package.json index f189c482500e..134e1eb957b8 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Not intended for use outside the Fluid client repo.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/common/core-utils/src/heap.ts b/packages/common/core-utils/src/heap.ts index e9768298ad6a..721f2360a9c0 100644 --- a/packages/common/core-utils/src/heap.ts +++ b/packages/common/core-utils/src/heap.ts @@ -67,7 +67,7 @@ export class Heap { * * @returns Heap node containing the smallest element */ - public peek(): IHeapNode { + public peek(): IHeapNode | undefined { return this.L[1]; } @@ -76,7 +76,11 @@ export class Heap { * * @returns The smallest value in the heap */ - public get(): T { + public get(): T | undefined { + if (this.L.length === 0) { + return undefined; + } + this.swap(1, this.count()); const x = this.L.pop(); this.fixdown(1); diff --git a/packages/common/driver-definitions/package.json b/packages/common/driver-definitions/package.json index 9478322d2de1..587ad153c67e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid driver definitions", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/cell/package.json b/packages/dds/cell/package.json index 941f3da3de3c..825677b440fe 100644 --- a/packages/dds/cell/package.json +++ b/packages/dds/cell/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/cell", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed data structure for a single value", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/cell/src/packageVersion.ts b/packages/dds/cell/src/packageVersion.ts index 76589e409b4f..2d01d731fa0f 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/counter/package.json b/packages/dds/counter/package.json index 6a75004f4d6f..f3ee8a881038 100644 --- a/packages/dds/counter/package.json +++ b/packages/dds/counter/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/counter", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Counter DDS", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/counter/src/packageVersion.ts b/packages/dds/counter/src/packageVersion.ts index f5626d678f74..3871ce97d0b9 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/ink/package.json b/packages/dds/ink/package.json index 5858588a1be9..050aa6f6abc8 100644 --- a/packages/dds/ink/package.json +++ b/packages/dds/ink/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/ink", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Ink DDS", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/ink/src/packageVersion.ts b/packages/dds/ink/src/packageVersion.ts index ccdc1c1b8fbc..42dba84cc1ce 100644 --- a/packages/dds/ink/src/packageVersion.ts +++ b/packages/dds/ink/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluidframework/ink"; -export const pkgVersion = "2.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/map/package.json b/packages/dds/map/package.json index 5cab74d00c2b..a16288837494 100644 --- a/packages/dds/map/package.json +++ b/packages/dds/map/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/map", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed map", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/map/src/packageVersion.ts b/packages/dds/map/src/packageVersion.ts index 704b1f8807af..f5a3f284a472 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/map/src/test/mocha/map.fuzz.spec.ts b/packages/dds/map/src/test/mocha/map.fuzz.spec.ts index e475be2042af..51278d75ee69 100644 --- a/packages/dds/map/src/test/mocha/map.fuzz.spec.ts +++ b/packages/dds/map/src/test/mocha/map.fuzz.spec.ts @@ -25,7 +25,7 @@ interface Clear { interface SetKey { type: "setKey"; key: string; - value: Jsonable; + value: Jsonable; } interface DeleteKey { diff --git a/packages/dds/matrix/api-report/matrix.api.md b/packages/dds/matrix/api-report/matrix.api.md index 489cca7152ba..bc8a347b76d9 100644 --- a/packages/dds/matrix/api-report/matrix.api.md +++ b/packages/dds/matrix/api-report/matrix.api.md @@ -27,20 +27,20 @@ import { SummarySerializer } from '@fluidframework/shared-object-base'; // @alpha (undocumented) export interface IRevertible { // (undocumented) - discard(): any; + discard(): void; // (undocumented) - revert(): any; + revert(): void; } // @alpha export interface ISharedMatrixEvents extends ISharedObjectEvents { - (event: "conflict", listener: (row: number, col: number, currentValue: MatrixItem, conflictingValue: MatrixItem, target: IEventThisPlaceHolder) => void): any; + (event: "conflict", listener: (row: number, col: number, currentValue: MatrixItem, conflictingValue: MatrixItem, target: IEventThisPlaceHolder) => void): void; } // @alpha (undocumented) export interface IUndoConsumer { // (undocumented) - pushToCurrentOperation(revertible: IRevertible): any; + pushToCurrentOperation(revertible: IRevertible): void; } // @alpha diff --git a/packages/dds/matrix/package.json b/packages/dds/matrix/package.json index 513b9b15ecee..18b1005ef552 100644 --- a/packages/dds/matrix/package.json +++ b/packages/dds/matrix/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/matrix", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed matrix", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/matrix/src/matrix.ts b/packages/dds/matrix/src/matrix.ts index 320b020ca5fc..fc0d47f1f74a 100644 --- a/packages/dds/matrix/src/matrix.ts +++ b/packages/dds/matrix/src/matrix.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { IEventThisPlaceHolder } from "@fluidframework/core-interfaces"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; @@ -29,6 +27,7 @@ import { MergeTreeDeltaType, IMergeTreeOp, SegmentGroup, + // eslint-disable-next-line import/no-deprecated Client, IJSONSegment, } from "@fluidframework/merge-tree"; @@ -101,7 +100,7 @@ export interface ISharedMatrixEvents extends ISharedObjectEvents { conflictingValue: MatrixItem, target: IEventThisPlaceHolder, ) => void, - ); + ): void; } /** @@ -608,6 +607,7 @@ export class SharedMatrix } private rebasePosition( + // eslint-disable-next-line import/no-deprecated client: Client, pos: number, referenceSequenceNumber: number, diff --git a/packages/dds/matrix/src/ops.ts b/packages/dds/matrix/src/ops.ts index bd5b5516be86..ff577dff0900 100644 --- a/packages/dds/matrix/src/ops.ts +++ b/packages/dds/matrix/src/ops.ts @@ -26,7 +26,7 @@ export interface IMatrixCellMsg extends IMatrixMsg { type: MatrixOp.set; row: number; col: number; - value: Serializable; + value: Serializable; } export interface IMatrixSwitchSetCellPolicy extends IMatrixMsg { diff --git a/packages/dds/matrix/src/packageVersion.ts b/packages/dds/matrix/src/packageVersion.ts index 8c3aeca87ffd..a10afd57130c 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/matrix/src/permutationvector.ts b/packages/dds/matrix/src/permutationvector.ts index c6ce55069fca..567b29b03dc0 100644 --- a/packages/dds/matrix/src/permutationvector.ts +++ b/packages/dds/matrix/src/permutationvector.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { createChildLogger } from "@fluidframework/telemetry-utils"; import { @@ -14,6 +12,7 @@ import { import { BaseSegment, ISegment, + // eslint-disable-next-line import/no-deprecated Client, IMergeTreeDeltaOpArgs, IMergeTreeDeltaCallbackArgs, @@ -120,6 +119,7 @@ export class PermutationSegment extends BaseSegment { } } +// eslint-disable-next-line import/no-deprecated export class PermutationVector extends Client { private handleTable = new HandleTable(); // Tracks available storage handles for rows. public readonly handleCache = new HandleCache(this); diff --git a/packages/dds/matrix/src/serialization.ts b/packages/dds/matrix/src/serialization.ts index 29bb54e02082..b80b9c10c20b 100644 --- a/packages/dds/matrix/src/serialization.ts +++ b/packages/dds/matrix/src/serialization.ts @@ -9,10 +9,10 @@ import { BlobTreeEntry } from "@fluidframework/driver-utils"; import { IFluidSerializer } from "@fluidframework/shared-object-base"; import { bufferToString } from "@fluid-internal/client-utils"; -export const serializeBlob = ( +export const serializeBlob = ( handle: IFluidHandle, path: string, - snapshot: Serializable, + snapshot: Serializable, serializer: IFluidSerializer, ) => new BlobTreeEntry(path, serializer.stringify(snapshot, handle)); diff --git a/packages/dds/matrix/src/test/matrix.spec.ts b/packages/dds/matrix/src/test/matrix.spec.ts index 87d493a608a7..d4d82be6450e 100644 --- a/packages/dds/matrix/src/test/matrix.spec.ts +++ b/packages/dds/matrix/src/test/matrix.spec.ts @@ -74,7 +74,7 @@ describe("Matrix1", () => { let matrix: SharedMatrix; // Test IMatrixConsumer that builds a copy of `matrix` via observed events. - let consumer: TestConsumer; + let consumer: TestConsumer; // Summarizes the given `SharedMatrix`, loads the summarize into a 2nd SharedMatrix, vets that the two are // equivalent, and then returns the 2nd matrix. diff --git a/packages/dds/matrix/src/test/matrix.undo.spec.ts b/packages/dds/matrix/src/test/matrix.undo.spec.ts index 847494ce637a..7f368695dab4 100644 --- a/packages/dds/matrix/src/test/matrix.undo.spec.ts +++ b/packages/dds/matrix/src/test/matrix.undo.spec.ts @@ -21,7 +21,7 @@ import { UndoRedoStackManager } from "./undoRedoStackManager"; let dataStoreRuntime: MockFluidDataStoreRuntime; let matrix1: SharedMatrix; // Test IMatrixConsumer that builds a copy of `matrix` via observed events. - let consumer1: TestConsumer; + let consumer1: TestConsumer; let undo1: UndoRedoStackManager; let expect: (expected: readonly (readonly MatrixItem[])[]) => Promise; diff --git a/packages/dds/matrix/src/test/undoRedoStackManager.ts b/packages/dds/matrix/src/test/undoRedoStackManager.ts index e8c9fc205a1f..772695e64561 100644 --- a/packages/dds/matrix/src/test/undoRedoStackManager.ts +++ b/packages/dds/matrix/src/test/undoRedoStackManager.ts @@ -55,7 +55,6 @@ class Stack { class UndoRedoStack extends Stack | undefined> { public push(item: Stack | undefined) { if (item !== undefined) { - // eslint-disable-next-line @typescript-eslint/unbound-method item.itemPushedCallback = () => this.callItemPushedCallback; } super.push(item); diff --git a/packages/dds/matrix/src/types.ts b/packages/dds/matrix/src/types.ts index 805f5461f8ea..2bb47739fa83 100644 --- a/packages/dds/matrix/src/types.ts +++ b/packages/dds/matrix/src/types.ts @@ -10,13 +10,13 @@ * @alpha */ export interface IRevertible { - revert(); - discard(); + revert(): void; + discard(): void; } /** * @alpha */ export interface IUndoConsumer { - pushToCurrentOperation(revertible: IRevertible); + pushToCurrentOperation(revertible: IRevertible): void; } diff --git a/packages/dds/matrix/tsconfig.json b/packages/dds/matrix/tsconfig.json index adc0e767997c..6728ced1f801 100644 --- a/packages/dds/matrix/tsconfig.json +++ b/packages/dds/matrix/tsconfig.json @@ -8,5 +8,6 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./dist", + "noImplicitAny": true, }, } diff --git a/packages/dds/merge-tree/api-report/merge-tree.api.md b/packages/dds/merge-tree/api-report/merge-tree.api.md index 79ef100c69c8..411ba4629996 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -5,6 +5,7 @@ ```ts import { AttributionKey } from '@fluidframework/runtime-definitions'; +import { Heap } from '@fluidframework/core-utils'; import { IChannelStorageService } from '@fluidframework/datastore-definitions'; import { IEventThisPlaceHolder } from '@fluidframework/core-interfaces'; import { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions'; @@ -16,12 +17,12 @@ import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; // @internal @deprecated (undocumented) -export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet, op?: ICombiningOp, seq?: number): PropertySet; +export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet): PropertySet; // @internal (undocumented) export function appendToMergeTreeDeltaRevertibles(deltaArgs: IMergeTreeDeltaCallbackArgs, revertibles: MergeTreeDeltaRevertible[]): void; -// @internal @sealed +// @alpha @sealed export interface AttributionPolicy { attach: (client: Client) => void; detach: () => void; @@ -32,10 +33,10 @@ export interface AttributionPolicy { // @alpha (undocumented) export abstract class BaseSegment extends MergeNode implements ISegment { - // @deprecated (undocumented) + // (undocumented) ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) protected addSerializedProps(jseg: IJSONSegment): void; // (undocumented) @@ -55,7 +56,9 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // (undocumented) hasProperty(key: string): boolean; // (undocumented) - isLeaf(): boolean; + isLeaf(): this is ISegment; + // (undocumented) + localMovedSeq?: number; // (undocumented) localRefs?: LocalReferenceCollection; // (undocumented) @@ -63,6 +66,12 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // (undocumented) localSeq?: number; // (undocumented) + movedClientIds?: number[]; + // (undocumented) + movedSeq?: number; + // (undocumented) + movedSeqs?: number[]; + // (undocumented) properties?: PropertySet; // (undocumented) propertyManager?: PropertiesManager; @@ -82,17 +91,17 @@ export abstract class BaseSegment extends MergeNode implements ISegment { readonly trackingCollection: TrackingGroupCollection; // (undocumented) abstract readonly type: string; + // (undocumented) + wasMovedOnInsert?: boolean | undefined; } // @alpha @deprecated (undocumented) export class Client extends TypedEventEmitter { - constructor(specToSegment: (spec: IJSONSegment) => ISegment, logger: ITelemetryLoggerExt, options?: PropertySet); + constructor(specToSegment: (spec: IJSONSegment) => ISegment, logger: ITelemetryLoggerExt, options?: IMergeTreeOptions & PropertySet); // (undocumented) addLongClientId(longClientId: string): void; - annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): IMergeTreeAnnotateMsg | undefined; - // @deprecated - annotateMarkerNotifyConsensus(marker: Marker, props: PropertySet, consensusCallback: (m: Marker) => void): IMergeTreeAnnotateMsg | undefined; - annotateRangeLocal(start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined): IMergeTreeAnnotateMsg | undefined; + annotateMarker(marker: Marker, props: PropertySet): IMergeTreeAnnotateMsg | undefined; + annotateRangeLocal(start: number, end: number, props: PropertySet): IMergeTreeAnnotateMsg | undefined; // (undocumented) applyMsg(msg: ISequencedDocumentMessage, local?: boolean): void; // (undocumented) @@ -101,8 +110,6 @@ export class Client extends TypedEventEmitter { applyStashedOp(op: IMergeTreeGroupMsg): SegmentGroup[]; // (undocumented) applyStashedOp(op: IMergeTreeOp): SegmentGroup | SegmentGroup[]; - // (undocumented) - cloneFromSegments(): Client; createLocalReferencePosition(segment: ISegment | "start" | "end", offset: number | undefined, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; // (undocumented) createTextHelper(): IMergeTreeTextHelper; @@ -140,9 +147,7 @@ export class Client extends TypedEventEmitter { posAfterEnd: number | undefined; }; // (undocumented) - getShortClientId(longClientId: string): number; - // @deprecated (undocumented) - getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap; + protected getShortClientId(longClientId: string): number; // (undocumented) insertAtReferencePositionLocal(refPos: ReferencePosition, segment: ISegment): IMergeTreeInsertMsg | undefined; // (undocumented) @@ -158,6 +163,7 @@ export class Client extends TypedEventEmitter { readonly logger: ITelemetryLoggerExt; // (undocumented) longClientId: string | undefined; + obliterateRangeLocal(start: number, end: number): IMergeTreeObliterateMsg; peekPendingSegmentGroups(count?: number): SegmentGroup | SegmentGroup[] | undefined; posFromRelativePos(relativePos: IRelativePosition): number; regeneratePendingOp(resetOp: IMergeTreeOp, segmentGroup: SegmentGroup | SegmentGroup[]): IMergeTreeOp; @@ -174,12 +180,8 @@ export class Client extends TypedEventEmitter { // (undocumented) summarize(runtime: IFluidDataStoreRuntime, handle: IFluidHandle, serializer: IFluidSerializer, catchUpMsgs: ISequencedDocumentMessage[]): ISummaryTreeWithStats; // (undocumented) - updateConsensusProperty(op: IMergeTreeAnnotateMsg, msg: ISequencedDocumentMessage): void; - // (undocumented) updateMinSeq(minSeq: number): void; // (undocumented) - updateSeqNumbers(min: number, seq: 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; @@ -187,9 +189,6 @@ export class Client extends TypedEventEmitter { walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; } -// @internal @deprecated (undocumented) -export function clone(extension: MapLike | undefined): MapLike | undefined; - // @alpha @deprecated (undocumented) export class CollaborationWindow { // (undocumented) @@ -203,26 +202,14 @@ export class CollaborationWindow { minSeq: number; } -// @internal @deprecated (undocumented) -export function combine(combiningInfo: ICombiningOp, currentValue: any, newValue: any, seq?: number): any; - -// @internal @deprecated (undocumented) -export const compareNumbers: (a: number, b: number) => number; - // @internal (undocumented) export function compareReferencePositions(a: ReferencePosition, b: ReferencePosition): number; -// @internal @deprecated (undocumented) -export const compareStrings: (a: string, b: string) => number; - // @internal (undocumented) export type ConflictAction = (key: TKey, currentKey: TKey, data: TData, currentData: TData) => QProperty; -// @internal @deprecated -export function createAnnotateMarkerOp(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): IMergeTreeAnnotateMsg | undefined; - -// @internal @deprecated -export function createAnnotateRangeOp(start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined): IMergeTreeAnnotateMsg; +// @internal +export function createAnnotateRangeOp(start: number, end: number, props: PropertySet): IMergeTreeAnnotateMsg; // @internal (undocumented) export function createDetachedLocalReferencePosition(refType?: ReferenceType): LocalReferencePosition; @@ -233,19 +220,22 @@ export function createGroupOp(...ops: IMergeTreeDeltaOp[]): IMergeTreeGroupMsg; // @internal (undocumented) export function createInsertOnlyAttributionPolicy(): AttributionPolicy; -// @internal @deprecated (undocumented) +// @internal (undocumented) export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg; -// @internal @deprecated (undocumented) +// @internal (undocumented) export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTreeInsertMsg; // @internal @deprecated (undocumented) export function createMap(): MapLike; -// @internal @deprecated +// @internal +export function createObliterateRangeOp(start: number, end: number): IMergeTreeObliterateMsg; + +// @internal export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemoveMsg; -// @internal (undocumented) +// @internal export function debugMarkerToString(marker: Marker): string; // @internal (undocumented) @@ -266,12 +256,6 @@ export interface Dictionary { // @internal (undocumented) export function discardMergeTreeDeltaRevertible(revertibles: MergeTreeDeltaRevertible[]): void; -// @internal @deprecated (undocumented) -export function extend(base: MapLike, extension: MapLike | undefined, combiningOp?: ICombiningOp, seq?: number): MapLike; - -// @internal @deprecated (undocumented) -export function extendIfUndefined(base: MapLike, extension: MapLike | undefined): MapLike; - // @internal export function getSlideToSegoff(segoff: { segment: ISegment | undefined; @@ -294,10 +278,10 @@ export interface IAttributionCollection { readonly length: number; // (undocumented) splitAt(pos: number): IAttributionCollection; - update(name: string | undefined, channel: IAttributionCollection): any; + update(name: string | undefined, channel: IAttributionCollection): void; } -// @internal @sealed (undocumented) +// @alpha @sealed (undocumented) export interface IAttributionCollectionSerializer { populateAttributionCollections(segments: Iterable, summary: SerializedAttributionCollection): void; // (undocumented) @@ -328,47 +312,11 @@ export interface IAttributionCollectionSpec { // @alpha export interface IClientEvents { // (undocumented) - (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): any; + (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void; // (undocumented) - (event: "delta", listener: (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, target: IEventThisPlaceHolder) => void): any; + (event: "delta", listener: (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, target: IEventThisPlaceHolder) => void): void; // (undocumented) - (event: "maintenance", listener: (args: IMergeTreeMaintenanceCallbackArgs, deltaArgs: IMergeTreeDeltaOpArgs | undefined, target: IEventThisPlaceHolder) => void): any; -} - -// @alpha @deprecated (undocumented) -export interface ICombiningOp { - // (undocumented) - defaultValue?: any; - // (undocumented) - maxValue?: any; - // (undocumented) - minValue?: any; - // (undocumented) - name: string; -} - -// @internal @deprecated (undocumented) -export interface IConsensusInfo { - // (undocumented) - callback: (m: Marker) => void; - // (undocumented) - marker: Marker; -} - -// @internal @deprecated (undocumented) -export interface IConsensusValue { - // (undocumented) - seq: number; - // (undocumented) - value: any; -} - -// @internal @deprecated -export interface IIntegerRange { - // (undocumented) - end: number; - // (undocumented) - start: number; + (event: "maintenance", listener: (args: IMergeTreeMaintenanceCallbackArgs, deltaArgs: IMergeTreeDeltaOpArgs | undefined, target: IEventThisPlaceHolder) => void): void; } // @alpha (undocumented) @@ -383,7 +331,7 @@ export interface IJSONSegment { props?: Record; } -// @internal (undocumented) +// @alpha (undocumented) export interface IJSONTextSegment extends IJSONSegment { // (undocumented) text: string; @@ -395,12 +343,6 @@ export interface IMarkerDef { refType?: ReferenceType; } -// @internal @deprecated (undocumented) -export interface IMarkerModifiedAction { - // (undocumented) - (marker: Marker): void; -} - // @alpha export interface IMergeNodeCommon { index: number; @@ -411,8 +353,6 @@ export interface IMergeNodeCommon { // @alpha (undocumented) export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { - // @deprecated (undocumented) - combiningOp?: ICombiningOp; // (undocumented) pos1?: number; // (undocumented) @@ -427,7 +367,7 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { type: typeof MergeTreeDeltaType.ANNOTATE; } -// @internal (undocumented) +// @alpha (undocumented) export interface IMergeTreeAttributionOptions { policyFactory?: () => AttributionPolicy; track?: boolean; @@ -457,7 +397,7 @@ export interface IMergeTreeDeltaCallbackArgs { } +// @alpha @deprecated (undocumented) +export interface IMergeTreeObliterateMsg extends IMergeTreeDelta { + // (undocumented) + pos1?: number; + // (undocumented) + pos2?: number; + relativePos1?: never; + relativePos2?: never; + // (undocumented) + type: typeof MergeTreeDeltaType.OBLITERATE; +} + // @alpha (undocumented) export type IMergeTreeOp = IMergeTreeDeltaOp | IMergeTreeGroupMsg; -// @internal (undocumented) +// @alpha (undocumented) export interface IMergeTreeOptions { attribution?: IMergeTreeAttributionOptions; // (undocumented) catchUpBlobName?: string; + mergeTreeEnableObliterate?: boolean; mergeTreeReferencesCanSlideToEndpoint?: boolean; // (undocumented) mergeTreeSnapshotChunkSize?: number; @@ -537,8 +490,15 @@ export interface IMergeTreeTextHelper { getText(refSeq: number, clientId: number, placeholder: string, start?: number, end?: number): string; } -// @internal @deprecated (undocumented) -export function internedSpaces(n: number): string; +// @alpha +export interface IMoveInfo { + localMovedSeq?: number; + movedClientIds: number[]; + movedSeq: number; + movedSeqs: number[]; + moveDst?: ReferencePosition; + wasMovedOnInsert: boolean; +} // @internal (undocumented) export interface IRBAugmentation { @@ -569,11 +529,9 @@ export interface IRemovalInfo { } // @alpha -export interface ISegment extends IMergeNodeCommon, Partial { - // @deprecated +export interface ISegment extends IMergeNodeCommon, Partial, Partial { ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; - // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) append(segment: ISegment): void; attribution?: IAttributionCollection; @@ -631,9 +589,6 @@ export interface KeyComparer { (a: TKey, b: TKey): number; } -// @internal @deprecated (undocumented) -export const LocalClientId = -1; - // @alpha export class LocalReferenceCollection { // (undocumented) @@ -653,15 +608,11 @@ export class LocalReferenceCollection { static append(seg1: ISegment, seg2: ISegment): void; append(other: LocalReferenceCollection): void; // (undocumented) - clear(): void; - // (undocumented) createLocalRef(offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; // (undocumented) get empty(): boolean; has(lref: ReferencePosition): boolean; // (undocumented) - hierRefCount: number; - // (undocumented) isAfterTombstone(lref: LocalReferencePosition): boolean; // (undocumented) removeLocalRef(lref: LocalReferencePosition): LocalReferencePosition | undefined; @@ -679,14 +630,14 @@ export interface LocalReferencePosition extends ReferencePosition { readonly trackingCollection: TrackingGroupCollection; } -// @alpha (undocumented) +// @alpha export interface MapLike { // (undocumented) [index: string]: T; } -// @alpha (undocumented) -export class Marker extends BaseSegment implements ReferencePosition { +// @alpha +export class Marker extends BaseSegment implements ReferencePosition, ISegment { constructor(refType: ReferenceType); // (undocumented) append(): void; @@ -707,8 +658,6 @@ export class Marker extends BaseSegment implements ReferencePosition { // (undocumented) getSegment(): this; // (undocumented) - hasSimpleType(simpleTypeName: string): boolean; - // (undocumented) static is(segment: ISegment): segment is Marker; // (undocumented) static make(refType: ReferenceType, props?: PropertySet): Marker; @@ -724,7 +673,7 @@ export class Marker extends BaseSegment implements ReferencePosition { readonly type = "Marker"; } -// @internal @deprecated (undocumented) +// @internal (undocumented) export function matchProperties(a: PropertySet | undefined, b: PropertySet | undefined): boolean; // @internal (undocumented) @@ -742,11 +691,8 @@ export class MergeNode implements IMergeNodeCommon { ordinal: string; } -// @internal @deprecated (undocumented) -export type MergeTreeDeltaCallback = (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs) => void; - // @alpha (undocumented) -export type MergeTreeDeltaOperationType = typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE; +export type MergeTreeDeltaOperationType = typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE | typeof MergeTreeDeltaType.OBLITERATE; // @alpha (undocumented) export type MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationType | MergeTreeMaintenanceType; @@ -770,14 +716,12 @@ export const MergeTreeDeltaType: { readonly REMOVE: 1; readonly ANNOTATE: 2; readonly GROUP: 3; + readonly OBLITERATE: 4; }; // @alpha (undocumented) export type MergeTreeDeltaType = (typeof MergeTreeDeltaType)[keyof typeof MergeTreeDeltaType]; -// @internal @deprecated (undocumented) -export type MergeTreeMaintenanceCallback = (MaintenanceArgs: IMergeTreeMaintenanceCallbackArgs, opArgs: IMergeTreeDeltaOpArgs | undefined) => void; - // @alpha export const MergeTreeMaintenanceType: { readonly APPEND: -1; @@ -792,26 +736,22 @@ export type MergeTreeMaintenanceType = (typeof MergeTreeMaintenanceType)[keyof t // @alpha (undocumented) export interface MergeTreeRevertibleDriver { // (undocumented) - annotateRange(start: number, end: number, props: PropertySet): any; + annotateRange(start: number, end: number, props: PropertySet): void; // (undocumented) - insertFromSpec(pos: number, spec: IJSONSegment): any; + insertFromSpec(pos: number, spec: IJSONSegment): void; // (undocumented) - removeRange(start: number, end: number): any; + removeRange(start: number, end: number): void; } // @internal (undocumented) export function minReferencePosition(a: T, b: T): T; -// @internal @deprecated (undocumented) -export const NonCollabClient = -2; - // @alpha (undocumented) export class PropertiesManager { - constructor(); // (undocumented) ackPendingProperties(annotateOp: IMergeTreeAnnotateMsg): void; // (undocumented) - addProperties(oldProps: PropertySet, newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(oldProps: PropertySet, newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) copyTo(oldProps: PropertySet, newProps: PropertySet | undefined, newManager: PropertiesManager): PropertySet | undefined; // (undocumented) @@ -823,8 +763,6 @@ export class PropertiesManager { // @alpha (undocumented) export enum PropertiesRollback { None = 0, - // @deprecated - Rewrite = 2, Rollback = 1 } @@ -842,7 +780,7 @@ export interface PropertyAction { (p: Property, accum?: TAccum): boolean; } -// @alpha (undocumented) +// @alpha export type PropertySet = MapLike; // @internal (undocumented) @@ -853,9 +791,6 @@ export interface QProperty { key?: TKey; } -// @alpha @deprecated (undocumented) -export type RangeStackMap = MapLike>; - // @internal (undocumented) export const RBColor: { readonly RED: 0; @@ -936,12 +871,11 @@ export class RedBlackTree implements SortedDictionary // @alpha export interface ReferencePosition { // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp): void; + addProperties(newProps: PropertySet): void; getOffset(): number; getSegment(): ISegment | undefined; // (undocumented) isLeaf(): this is ISegment; - // (undocumented) properties?: PropertySet; // (undocumented) refType: ReferenceType; @@ -950,13 +884,7 @@ export interface ReferencePosition { // @alpha export enum ReferenceType { - // @deprecated (undocumented) - NestBegin = 2, - // @deprecated (undocumented) - NestEnd = 4, - // (undocumented) RangeBegin = 16, - // (undocumented) RangeEnd = 32, // (undocumented) Simple = 0, @@ -966,18 +894,9 @@ export enum ReferenceType { Transient = 256 } -// @internal @deprecated (undocumented) -export const refGetRangeLabels: (refPos: ReferencePosition) => string[] | undefined; - // @internal (undocumented) export const refGetTileLabels: (refPos: ReferencePosition) => string[] | undefined; -// @internal @deprecated (undocumented) -export function refHasRangeLabel(refPos: ReferencePosition, label: string): boolean; - -// @internal @deprecated (undocumented) -export function refHasRangeLabels(refPos: ReferencePosition): boolean; - // @internal (undocumented) export function refHasTileLabel(refPos: ReferencePosition, label: string): boolean; @@ -987,7 +906,7 @@ export function refHasTileLabels(refPos: ReferencePosition): boolean; // @internal (undocumented) export function refTypeIncludesFlag(refPosOrType: ReferencePosition | ReferenceType, flags: ReferenceType): boolean; -// @internal (undocumented) +// @internal export const reservedMarkerIdKey = "markerId"; // @internal (undocumented) @@ -1002,22 +921,16 @@ export const reservedTileLabelsKey = "referenceTileLabels"; // @internal (undocumented) export function revertMergeTreeDeltaRevertibles(driver: MergeTreeRevertibleDriver, revertibles: MergeTreeDeltaRevertible[]): void; -// @internal @deprecated (undocumented) -export interface SegmentAccumulator { - // (undocumented) - segments: ISegment[]; -} - // @alpha @deprecated (undocumented) export interface SegmentGroup { // (undocumented) - localSeq: number; + localSeq?: number; // (undocumented) previousProps?: PropertySet[]; // (undocumented) refSeq: number; // (undocumented) - segments: ISegment[]; + segments: ISegmentLeaf[]; } // @alpha (undocumented) @@ -1025,26 +938,26 @@ export class SegmentGroupCollection { constructor(segment: ISegment); // (undocumented) copyTo(segment: ISegment): void; - // @deprecated (undocumented) + // (undocumented) dequeue(): SegmentGroup | undefined; // (undocumented) get empty(): boolean; - // @deprecated (undocumented) + // (undocumented) enqueue(segmentGroup: SegmentGroup): void; - // @deprecated (undocumented) + // (undocumented) pop?(): SegmentGroup | undefined; // (undocumented) get size(): number; } -// @internal (undocumented) +// @alpha (undocumented) export interface SequenceOffsets { // (undocumented) posBreakpoints: number[]; seqs: (number | AttributionKey | null)[]; } -// @internal (undocumented) +// @alpha (undocumented) export interface SerializedAttributionCollection extends SequenceOffsets { // (undocumented) channels?: { @@ -1112,20 +1025,6 @@ export abstract class SortedSet { get size(): number; } -// @alpha @deprecated (undocumented) -export class Stack { - // (undocumented) - empty(): boolean; - // (undocumented) - items: T[]; - // (undocumented) - pop(): T | undefined; - // (undocumented) - push(val: T): void; - // (undocumented) - top(): T | undefined; -} - // @alpha (undocumented) export class TextSegment extends BaseSegment { constructor(text: string); @@ -1144,14 +1043,9 @@ export class TextSegment extends BaseSegment { // (undocumented) static make(text: string, props?: PropertySet): TextSegment; // (undocumented) - removeRange(start: number, end: number): boolean; - // (undocumented) text: string; // (undocumented) - toJSONObject(): string | { - text: string; - props: PropertySet; - }; + toJSONObject(): IJSONTextSegment | string; // (undocumented) toString(): string; // (undocumented) @@ -1160,7 +1054,7 @@ export class TextSegment extends BaseSegment { readonly type = "TextSegment"; } -// @internal @deprecated (undocumented) +// @internal (undocumented) export function toRemovalInfo(maybe: Partial | undefined): IRemovalInfo | undefined; // @alpha (undocumented) @@ -1181,7 +1075,7 @@ export class TrackingGroup implements ITrackingGroup { unlink(trackable: Trackable): boolean; } -// @alpha (undocumented) +// @alpha export class TrackingGroupCollection { constructor(trackable: Trackable); // (undocumented) @@ -1198,13 +1092,10 @@ export class TrackingGroupCollection { unlink(trackingGroup: ITrackingGroup): boolean; } -// @internal @deprecated (undocumented) -export const TreeMaintenanceSequenceNumber = -2; - -// @internal @deprecated (undocumented) +// @internal export const UnassignedSequenceNumber = -1; -// @internal @deprecated +// @internal export const UniversalSequenceNumber = 0; // (No @packageDocumentation comment for this package) diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index 3652373df4cf..659e2c830371 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Merge tree", "homepage": "https://fluidframework.com", "repository": { @@ -123,6 +123,143 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedInterfaceDeclaration_IIntegerRange": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_internedSpaces": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_refGetRangeLabels": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_refHasRangeLabel": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_refHasRangeLabels": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_Client": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedTypeAliasDeclaration_RangeStackMap": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_Stack": { + "forwardCompat": false, + "backCompat": false + }, + "FunctionDeclaration_extendIfUndefined": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_clone": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_combine": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_extend": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_extendIfUndefined": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_SegmentGroup": { + "backCompat": false + }, + "RemovedInterfaceDeclaration_Dictionary": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_IConsensusInfo": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_IConsensusValue": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_IMarkerModifiedAction": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_LocalClientId": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedTypeAliasDeclaration_MergeTreeDeltaCallback": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedTypeAliasDeclaration_MergeTreeMaintenanceCallback": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_NonCollabClient": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_SegmentAccumulator": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedInterfaceDeclaration_SortedDictionary": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_TreeMaintenanceSequenceNumber": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_compareNumbers": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_compareStrings": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedEnumDeclaration_PropertiesRollback": { + "forwardCompat": false + }, + "ClassDeclaration_LocalReferenceCollection": { + "backCompat": false + }, + "ClassDeclaration_BaseSegment": { + "backCompat": false + }, + "InterfaceDeclaration_IConsensusInfo": { + "backCompat": false + }, + "ClassDeclaration_Marker": { + "backCompat": false + }, + "ClassDeclaration_TextSegment": { + "backCompat": false + }, + "RemovedInterfaceDeclaration_ICombiningOp": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_createAnnotateMarkerOp": { + "forwardCompat": false, + "backCompat": false + }, + "VariableDeclaration_MergeTreeDeltaType": { + "forwardCompat": false + } + } } } diff --git a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts index 531534aee852..c58c7701969d 100644 --- a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts +++ b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - -import { IIntegerRange } from "./base"; +import { IIntegerRange } from "./client"; import { ISegment } from "./mergeTreeNodes"; import { MergeTree } from "./mergeTree"; +// eslint-disable-next-line import/no-deprecated import { IMergeTreeTextHelper, TextSegment } from "./textSegment"; interface ITextAccumulator { @@ -16,6 +15,7 @@ interface ITextAccumulator { parallelArrays?: boolean; } +// eslint-disable-next-line import/no-deprecated export class MergeTreeTextHelper implements IMergeTreeTextHelper { constructor(private readonly mergeTree: MergeTree) {} diff --git a/packages/dds/merge-tree/src/attributionCollection.ts b/packages/dds/merge-tree/src/attributionCollection.ts index 7bfb724d364a..e59a6231ec1d 100644 --- a/packages/dds/merge-tree/src/attributionCollection.ts +++ b/packages/dds/merge-tree/src/attributionCollection.ts @@ -12,7 +12,7 @@ import { import { ISegment } from "./mergeTreeNodes"; /** - * @internal + * @alpha */ export interface SequenceOffsets { /** @@ -27,12 +27,13 @@ export interface SequenceOffsets { * @remarks We use null here rather than undefined as round-tripping through JSON converts * undefineds to null anyway */ + // eslint-disable-next-line @rushstack/no-new-null seqs: (number | AttributionKey | null)[]; posBreakpoints: number[]; } /** - * @internal + * @alpha */ export interface SerializedAttributionCollection extends SequenceOffsets { channels?: { [name: string]: SequenceOffsets }; @@ -44,13 +45,15 @@ export interface SerializedAttributionCollection extends SequenceOffsets { * @alpha */ export interface IAttributionCollectionSpec { + // eslint-disable-next-line @rushstack/no-new-null root: Iterable<{ offset: number; key: T | null }>; + // eslint-disable-next-line @rushstack/no-new-null channels?: { [name: string]: Iterable<{ offset: number; key: T | null }> }; length: number; } /** - * @internal + * @alpha * @sealed */ export interface IAttributionCollectionSerializer { @@ -112,12 +115,14 @@ export interface IAttributionCollection { * channel and 4 other channels, it should call `.update` 5 times). * @param channel - Updated collection for that channel. */ - update(name: string | undefined, channel: IAttributionCollection); + update(name: string | undefined, channel: IAttributionCollection): void; } // note: treats null and undefined as equivalent export function areEqualAttributionKeys( + // eslint-disable-next-line @rushstack/no-new-null a: AttributionKey | null | undefined, + // eslint-disable-next-line @rushstack/no-new-null b: AttributionKey | null | undefined, ): boolean { if (!a && !b) { @@ -157,6 +162,7 @@ export class AttributionCollection implements IAttributionCollection { - const root: IAttributionCollectionSpec["root"] = new Array( - this.keys.length, - ); + type ExtractGeneric = T extends Iterable ? Q : unknown; + const root: ExtractGeneric["root"]>[] = + new Array(this.keys.length); for (let i = 0; i < this.keys.length; i++) { root[i] = { offset: this.offsets[i], key: this.keys[i] }; } @@ -277,7 +283,7 @@ export class AttributionCollection implements IAttributionCollection = {}; for (const [key, collection] of this.channelEntries) { channelsCopy[key] = collection.clone(); } diff --git a/packages/dds/merge-tree/src/attributionPolicy.ts b/packages/dds/merge-tree/src/attributionPolicy.ts index a5b472e7ed10..f286f093ba54 100644 --- a/packages/dds/merge-tree/src/attributionPolicy.ts +++ b/packages/dds/merge-tree/src/attributionPolicy.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { AttributionKey } from "@fluidframework/runtime-definitions"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { AttributionPolicy } from "./mergeTree"; +// eslint-disable-next-line import/no-deprecated import { Client } from "./client"; import { IMergeTreeDeltaCallbackArgs, @@ -27,11 +26,13 @@ interface AttributionCallbacks { delta: ( opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, + // eslint-disable-next-line import/no-deprecated client: Client, ) => void; maintenance: ( maintenanceArgs: IMergeTreeMaintenanceCallbackArgs, opArgs: IMergeTreeDeltaOpArgs | undefined, + // eslint-disable-next-line import/no-deprecated client: Client, ) => void; } @@ -42,14 +43,17 @@ function createAttributionPolicyFromCallbacks({ }: AttributionCallbacks): AttributionPolicy { let unsubscribe: undefined | (() => void); return { + // eslint-disable-next-line import/no-deprecated attach: (client: Client) => { assert( unsubscribe === undefined, 0x557 /* cannot attach to multiple clients at once */, ); - const deltaSubscribed = (opArgs, deltaArgs) => delta(opArgs, deltaArgs, client); - const maintenanceSubscribed = (args, opArgs) => maintenance(args, opArgs, client); + const deltaSubscribed: AttributionCallbacks["delta"] = (opArgs, deltaArgs) => + delta(opArgs, deltaArgs, client); + const maintenanceSubscribed: AttributionCallbacks["maintenance"] = (args, opArgs) => + maintenance(args, opArgs, client); client.on("delta", deltaSubscribed); client.on("maintenance", maintenanceSubscribed); @@ -82,6 +86,7 @@ const ensureAttributionCollectionCallbacks: AttributionCallbacks = { }; const getAttributionKey = ( + // eslint-disable-next-line import/no-deprecated client: Client, msg: ISequencedDocumentMessage | undefined, ): AttributionKey => { diff --git a/packages/dds/merge-tree/src/base.ts b/packages/dds/merge-tree/src/base.ts deleted file mode 100644 index 4ca2aab73847..000000000000 --- a/packages/dds/merge-tree/src/base.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * A range [start, end) - * @deprecated for internal use only. public export will be removed. - * @internal - */ -export interface IIntegerRange { - start: number; - end: number; -} diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index eaa29a0d1c04..7aff0d3a5e9b 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { IFluidHandle, type IEventThisPlaceHolder } from "@fluidframework/core-interfaces"; @@ -17,36 +16,35 @@ import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions"; import { assert, unreachableCase } from "@fluidframework/core-utils"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; import { ITelemetryLoggerExt, LoggingError, UsageError } from "@fluidframework/telemetry-utils"; -import { IIntegerRange } from "./base"; -import { List, RedBlackTree } from "./collections"; +import { DoublyLinkedList, RedBlackTree } from "./collections"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; import { LocalReferencePosition, SlidingPreference } from "./localReference"; import { + // eslint-disable-next-line import/no-deprecated CollaborationWindow, compareStrings, - IConsensusInfo, + IMoveInfo, IMergeLeaf, ISegment, ISegmentAction, Marker, + // eslint-disable-next-line import/no-deprecated SegmentGroup, } from "./mergeTreeNodes"; -import { - IMergeTreeDeltaCallbackArgs, - IMergeTreeMaintenanceCallbackArgs, -} from "./mergeTreeDeltaCallback"; import { createAnnotateMarkerOp, createAnnotateRangeOp, + // eslint-disable-next-line import/no-deprecated createGroupOp, createInsertSegmentOp, + createObliterateRangeOp, createRemoveRangeOp, } from "./opBuilder"; import { - ICombiningOp, IJSONSegment, IMergeTreeAnnotateMsg, IMergeTreeDeltaOp, + // eslint-disable-next-line import/no-deprecated IMergeTreeGroupMsg, IMergeTreeInsertMsg, IMergeTreeRemoveMsg, @@ -54,21 +52,46 @@ import { IRelativePosition, MergeTreeDeltaType, ReferenceType, + // eslint-disable-next-line import/no-deprecated + IMergeTreeObliterateMsg, } from "./ops"; import { PropertySet } from "./properties"; import { SnapshotLegacy } from "./snapshotlegacy"; import { SnapshotLoader } from "./snapshotLoader"; +// eslint-disable-next-line import/no-deprecated import { IMergeTreeTextHelper } from "./textSegment"; import { SnapshotV1 } from "./snapshotV1"; -import { ReferencePosition, RangeStackMap, DetachedReferencePosition } from "./referencePositions"; -import { MergeTree } from "./mergeTree"; +import { ReferencePosition, DetachedReferencePosition } from "./referencePositions"; +import { IMergeTreeOptions, MergeTree } from "./mergeTree"; import { MergeTreeTextHelper } from "./MergeTreeTextHelper"; import { walkAllChildSegments } from "./mergeTreeNodeWalk"; -import { IMergeTreeClientSequenceArgs, IMergeTreeDeltaOpArgs } from "./index"; +import { + IMergeTreeClientSequenceArgs, + IMergeTreeDeltaCallbackArgs, + IMergeTreeDeltaOpArgs, + IMergeTreeMaintenanceCallbackArgs, +} from "./index"; type IMergeTreeDeltaRemoteOpArgs = Omit & Required>; +function removeMoveInfo(segment: Partial): void { + delete segment.movedSeq; + delete segment.movedSeqs; + delete segment.localMovedSeq; + delete segment.movedClientIds; + delete segment.wasMovedOnInsert; +} + +/** + * A range [start, end) + * @internal + */ +export interface IIntegerRange { + start: number; + end: number; +} + /** * 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 @@ -76,7 +99,7 @@ type IMergeTreeDeltaRemoteOpArgs = Omit void); + (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void; ( event: "delta", listener: ( @@ -84,7 +107,7 @@ export interface IClientEvents { deltaArgs: IMergeTreeDeltaCallbackArgs, target: IEventThisPlaceHolder, ) => void, - ); + ): void; ( event: "maintenance", listener: ( @@ -92,7 +115,7 @@ export interface IClientEvents { deltaArgs: IMergeTreeDeltaOpArgs | undefined, target: IEventThisPlaceHolder, ) => void, - ); + ): void; } /** @@ -106,13 +129,12 @@ export class Client extends TypedEventEmitter { private readonly clientNameToIds = new RedBlackTree(compareStrings); private readonly shortClientIdMap: string[] = []; - private readonly pendingConsensus = new Map(); constructor( // Passing this callback would be unnecessary if Client were merged with SharedSegmentSequence public readonly specToSegment: (spec: IJSONSegment) => ISegment, public readonly logger: ITelemetryLoggerExt, - options?: PropertySet, + options?: IMergeTreeOptions & PropertySet, ) { super(); this._mergeTree = new MergeTree(options); @@ -141,12 +163,14 @@ export class Client extends TypedEventEmitter { * It is used to get the segment group(s) for the previous operations. * @param count - The number segment groups to get peek from the tail of the queue. Default 1. */ + // eslint-disable-next-line import/no-deprecated public peekPendingSegmentGroups(count: number = 1): SegmentGroup | SegmentGroup[] | undefined { const pending = this._mergeTree.pendingSegments; let node = pending?.last; if (count === 1 || pending === undefined) { return node?.data; } + // eslint-disable-next-line import/no-deprecated const taken: SegmentGroup[] = new Array(Math.min(count, pending.length)); for (let i = taken.length - 1; i >= 0; i--) { taken[i] = node!.data; @@ -155,75 +179,33 @@ export class Client extends TypedEventEmitter { return taken; } - /** - * Annotate a marker and call the callback on consensus. - * @param marker - The marker to annotate - * @param props - The properties to annotate the marker with - * @param consensusCallback - The callback called when consensus is reached - * @returns The annotate op if valid, otherwise undefined - * - * @deprecated We no longer intend to support this functionality and it will - * be removed in a future release. There is no replacement for this - * functionality. - */ - public annotateMarkerNotifyConsensus( - marker: Marker, - props: PropertySet, - consensusCallback: (m: Marker) => void, - ): IMergeTreeAnnotateMsg | undefined { - const combiningOp: ICombiningOp = { - name: "consensus", - }; - - const annotateOp = this.annotateMarker(marker, props, combiningOp); - - if (annotateOp) { - const consensusInfo: IConsensusInfo = { - callback: consensusCallback, - marker, - }; - this.pendingConsensus.set(marker.getId()!, consensusInfo); - return annotateOp; - } else { - return undefined; - } - } /** * Annotates the markers with the provided properties * @param marker - The marker to annotate * @param props - The properties to annotate the marker with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * @returns The annotate op if valid, otherwise undefined */ - public annotateMarker( - marker: Marker, - props: PropertySet, - combiningOp?: ICombiningOp, - ): IMergeTreeAnnotateMsg | undefined { - const annotateOp = createAnnotateMarkerOp(marker, props, combiningOp)!; - - return this.applyAnnotateRangeOp({ op: annotateOp }) ? annotateOp : undefined; + public annotateMarker(marker: Marker, props: PropertySet): IMergeTreeAnnotateMsg | undefined { + const annotateOp = createAnnotateMarkerOp(marker, props)!; + this.applyAnnotateRangeOp({ op: annotateOp }); + return annotateOp; } + /** * Annotates the range with the provided properties * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with - * @param combiningOp - Specifies how to combine values for the property, such as "incr" for increment. * @returns The annotate op if valid, otherwise undefined */ public annotateRangeLocal( start: number, end: number, props: PropertySet, - combiningOp: ICombiningOp | undefined, ): IMergeTreeAnnotateMsg | undefined { - const annotateOp = createAnnotateRangeOp(start, end, props, combiningOp); - - if (this.applyAnnotateRangeOp({ op: annotateOp })) { - return annotateOp; - } - return undefined; + const annotateOp = createAnnotateRangeOp(start, end, props); + this.applyAnnotateRangeOp({ op: annotateOp }); + return annotateOp; } /** @@ -238,6 +220,20 @@ export class Client extends TypedEventEmitter { return removeOp; } + /** + * Obliterates the range. This is similar to removing the range, but also + * includes any concurrently inserted content. + * + * @param start - The inclusive start of the range to obliterate + * @param end - The exclusive end of the range to obliterate + */ + // eslint-disable-next-line import/no-deprecated + public obliterateRangeLocal(start: number, end: number): IMergeTreeObliterateMsg { + const obliterateOp = createObliterateRangeOp(start, end); + this.applyObliterateRangeOp({ op: obliterateOp }); + return obliterateOp; + } + /** * @param pos - The position to insert the segment at * @param segment - The segment to insert @@ -353,6 +349,7 @@ export class Client extends TypedEventEmitter { } } + // eslint-disable-next-line import/no-deprecated public getCollabWindow(): CollaborationWindow { return this._mergeTree.collabWindow; } @@ -435,15 +432,35 @@ export class Client extends TypedEventEmitter { * Revert an op */ public rollback?(op: any, localOpMetadata: unknown) { + // eslint-disable-next-line import/no-deprecated this._mergeTree.rollback(op as IMergeTreeDeltaOp, localOpMetadata as SegmentGroup); } + private applyObliterateRangeOp(opArgs: IMergeTreeDeltaOpArgs): void { + assert( + opArgs.op.type === MergeTreeDeltaType.OBLITERATE, + "Unexpected op type on range obliterate!", + ); + const op = opArgs.op; + const clientArgs = this.getClientSequenceArgs(opArgs); + const range = this.getValidOpRange(op, clientArgs); + + this._mergeTree.obliterateRange( + range.start, + range.end, + clientArgs.referenceSequenceNumber, + clientArgs.clientId, + clientArgs.sequenceNumber, + false, + opArgs, + ); + } + /** * Performs the remove based on the provided op * @param opArgs - The ops args for the op - * @returns True if the remove was applied. False if it could not be. */ - private applyRemoveRangeOp(opArgs: IMergeTreeDeltaOpArgs): boolean { + private applyRemoveRangeOp(opArgs: IMergeTreeDeltaOpArgs): void { assert( opArgs.op.type === MergeTreeDeltaType.REMOVE, 0x02d /* "Unexpected op type on range remove!" */, @@ -461,16 +478,13 @@ export class Client extends TypedEventEmitter { false, opArgs, ); - - return true; } /** * Performs the annotate based on the provided op * @param opArgs - The ops args for the op - * @returns True if the annotate was applied. False if it could not be. */ - private applyAnnotateRangeOp(opArgs: IMergeTreeDeltaOpArgs): boolean { + private applyAnnotateRangeOp(opArgs: IMergeTreeDeltaOpArgs): void { assert( opArgs.op.type === MergeTreeDeltaType.ANNOTATE, 0x02e /* "Unexpected op type on range annotate!" */, @@ -479,22 +493,15 @@ export class Client extends TypedEventEmitter { const clientArgs = this.getClientSequenceArgs(opArgs); const range = this.getValidOpRange(op, clientArgs); - if (!range) { - return false; - } - this._mergeTree.annotateRange( range.start, range.end, op.props, - op.combiningOp, clientArgs.referenceSequenceNumber, clientArgs.clientId, clientArgs.sequenceNumber, opArgs, ); - - return true; } /** @@ -511,10 +518,6 @@ export class Client extends TypedEventEmitter { const clientArgs = this.getClientSequenceArgs(opArgs); const range = this.getValidOpRange(op, clientArgs); - if (!range) { - return false; - } - let segments: ISegment[] | undefined; if (op.seg) { segments = [this.specToSegment(op.seg)]; @@ -532,6 +535,7 @@ export class Client extends TypedEventEmitter { clientArgs.sequenceNumber, opArgs, ); + return true; } @@ -541,7 +545,12 @@ export class Client extends TypedEventEmitter { * @param clientArgs - The client args for the op */ private getValidOpRange( - op: IMergeTreeAnnotateMsg | IMergeTreeInsertMsg | IMergeTreeRemoveMsg, + op: + | IMergeTreeAnnotateMsg + | IMergeTreeInsertMsg + | IMergeTreeRemoveMsg + // eslint-disable-next-line import/no-deprecated + | IMergeTreeObliterateMsg, clientArgs: IMergeTreeClientSequenceArgs, ): IIntegerRange { let start: number | undefined = op.pos1; @@ -586,6 +595,10 @@ export class Client extends TypedEventEmitter { } } + if (op.type === MergeTreeDeltaType.OBLITERATE && end !== undefined && end > length) { + invalidPositions.push("end"); + } + if (invalidPositions.length > 0) { throw new LoggingError("RangeOutOfBounds", { usageError: true, @@ -603,8 +616,7 @@ export class Client extends TypedEventEmitter { } // start and end are guaranteed to be non-null here, otherwise we throw above. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return { start, end } as IIntegerRange; + return { start: start!, end: end! }; } /** @@ -647,37 +659,19 @@ export class Client extends TypedEventEmitter { } private ackPendingSegment(opArgs: IMergeTreeDeltaRemoteOpArgs) { - const ackOp = (deltaOpArgs: IMergeTreeDeltaRemoteOpArgs) => { - this._mergeTree.ackPendingSegment(deltaOpArgs); - if (deltaOpArgs.op.type === MergeTreeDeltaType.ANNOTATE) { - if (deltaOpArgs.op.combiningOp && deltaOpArgs.op.combiningOp.name === "consensus") { - this.updateConsensusProperty(deltaOpArgs.op, deltaOpArgs.sequencedMessage); - } - } - }; - if (opArgs.op.type === MergeTreeDeltaType.GROUP) { for (const memberOp of opArgs.op.ops) { - ackOp({ + this._mergeTree.ackPendingSegment({ groupOp: opArgs.op, op: memberOp, sequencedMessage: opArgs.sequencedMessage, }); } } else { - ackOp(opArgs); + this._mergeTree.ackPendingSegment(opArgs); } } - // as functions are modified move them above the eslint-disabled waterline and lint them - - cloneFromSegments() { - const clone = new Client(this.specToSegment, this.logger, this._mergeTree.options); - const segments: ISegment[] = []; - const newRoot = this._mergeTree.blockClone(this._mergeTree.root, segments); - clone._mergeTree.root = newRoot; - return clone; - } getOrAddShortClientId(longClientId: string) { if (!this.clientNameToIds.get(longClientId)) { this.addLongClientId(longClientId); @@ -685,16 +679,19 @@ export class Client extends TypedEventEmitter { return this.getShortClientId(longClientId); } - getShortClientId(longClientId: string) { + protected getShortClientId(longClientId: string) { return this.clientNameToIds.get(longClientId)!.data; } + getLongClientId(shortClientId: number) { return shortClientId >= 0 ? this.shortClientIdMap[shortClientId] : "original"; } + addLongClientId(longClientId: string) { this.clientNameToIds.put(longClientId, this.shortClientIdMap.length); this.shortClientIdMap.push(longClientId); } + private getOrAddShortClientIdFromMessage(msg: Pick) { return this.getOrAddShortClientId(msg.clientId ?? "server"); } @@ -719,6 +716,7 @@ export class Client extends TypedEventEmitter { private resetPendingDeltaToOps( resetOp: IMergeTreeDeltaOp, + // eslint-disable-next-line import/no-deprecated segmentGroup: SegmentGroup, ): IMergeTreeDeltaOp[] { assert(!!segmentGroup, 0x033 /* "Segment group undefined" */); @@ -731,6 +729,14 @@ export class Client extends TypedEventEmitter { this.pendingRebase = undefined; } + // if this is an obliterate op, keep all segments in same segment group + // eslint-disable-next-line import/no-deprecated + const obliterateSegmentGroup: SegmentGroup = { + segments: [], + localSeq: segmentGroup.localSeq, + refSeq: this.getCollabWindow().currentSeq, + }; + const opList: IMergeTreeDeltaOp[] = []; // We need to sort the segments by ordinal, as the segments are not sorted in the segment group. // The reason they need them sorted, as they have the same local sequence number and which means @@ -745,6 +751,10 @@ export class Client extends TypedEventEmitter { segmentGroup === segmentSegGroup, 0x035 /* "Segment group not at head of segment pending queue" */, ); + assert( + segmentGroup.localSeq !== undefined, + "expected segment group localSeq to be defined", + ); const segmentPosition = this.findReconnectionPosition(segment, segmentGroup.localSeq); let newOp: IMergeTreeDeltaOp | undefined; switch (resetOp.type) { @@ -753,19 +763,21 @@ export class Client extends TypedEventEmitter { segment.propertyManager?.hasPendingProperties() === true, 0x036 /* "Segment has no pending properties" */, ); - // if the segment has been removed, there's no need to send the annotate op + // if the segment has been removed or obliterated, there's no need to send the annotate op // unless the remove was local, in which case the annotate must have come // before the remove if ( - segment.removedSeq === undefined || - (segment.localRemovedSeq !== undefined && - segment.removedSeq === UnassignedSequenceNumber) + (segment.removedSeq === undefined || + (segment.localRemovedSeq !== undefined && + segment.removedSeq === UnassignedSequenceNumber)) && + (segment.movedSeq === undefined || + (segment.localMovedSeq !== undefined && + segment.movedSeq === UnassignedSequenceNumber)) ) { newOp = createAnnotateRangeOp( segmentPosition, segmentPosition + segment.cachedLength, resetOp.props, - resetOp.combiningOp, ); } break; @@ -780,13 +792,19 @@ export class Client extends TypedEventEmitter { segInsertOp = segment.clone(); segInsertOp.properties = resetOp.seg.props; } + if (segment.movedSeq !== UnassignedSequenceNumber) { + removeMoveInfo(segment); + } newOp = createInsertSegmentOp(segmentPosition, segInsertOp); break; case MergeTreeDeltaType.REMOVE: if ( segment.localRemovedSeq !== undefined && - segment.removedSeq === UnassignedSequenceNumber + segment.removedSeq === UnassignedSequenceNumber && + (segment.movedSeq === undefined || + (segment.localMovedSeq !== undefined && + segment.movedSeq === UnassignedSequenceNumber)) ) { newOp = createRemoveRangeOp( segmentPosition, @@ -794,23 +812,56 @@ export class Client extends TypedEventEmitter { ); } break; - + case MergeTreeDeltaType.OBLITERATE: + if ( + segment.localMovedSeq !== undefined && + segment.movedSeq === UnassignedSequenceNumber && + (segment.removedSeq === undefined || + (segment.localRemovedSeq !== undefined && + segment.removedSeq === UnassignedSequenceNumber)) + ) { + newOp = createObliterateRangeOp( + segmentPosition, + segmentPosition + segment.cachedLength, + ); + } + break; default: throw new Error(`Invalid op type`); } - if (newOp) { + if (newOp && resetOp.type === MergeTreeDeltaType.OBLITERATE) { + segment.segmentGroups.enqueue(obliterateSegmentGroup); + + const first = opList[0]; + + if (!!first && first.pos2 !== undefined) { + first.pos2 += newOp.pos2! - newOp.pos1!; + } else { + opList.push(newOp); + } + } else if (newOp) { + // eslint-disable-next-line import/no-deprecated const newSegmentGroup: SegmentGroup = { segments: [], localSeq: segmentGroup.localSeq, refSeq: this.getCollabWindow().currentSeq, }; segment.segmentGroups.enqueue(newSegmentGroup); + this._mergeTree.pendingSegments.push(newSegmentGroup); + opList.push(newOp); } } + if ( + resetOp.type === MergeTreeDeltaType.OBLITERATE && + obliterateSegmentGroup.segments.length > 0 + ) { + this._mergeTree.pendingSegments.push(obliterateSegmentGroup); + } + return opList; } @@ -828,6 +879,9 @@ export class Client extends TypedEventEmitter { case MergeTreeDeltaType.ANNOTATE: this.applyAnnotateRangeOp(opArgs); break; + case MergeTreeDeltaType.OBLITERATE: + this.applyObliterateRangeOp(opArgs); + break; case MergeTreeDeltaType.GROUP: { for (const memberOp of op.ops) { this.applyRemoteOp({ @@ -843,10 +897,15 @@ export class Client extends TypedEventEmitter { } } + // eslint-disable-next-line import/no-deprecated public applyStashedOp(op: IMergeTreeDeltaOp): SegmentGroup; + // eslint-disable-next-line import/no-deprecated public applyStashedOp(op: IMergeTreeGroupMsg): SegmentGroup[]; + // eslint-disable-next-line import/no-deprecated public applyStashedOp(op: IMergeTreeOp): SegmentGroup | SegmentGroup[]; + // eslint-disable-next-line import/no-deprecated public applyStashedOp(op: IMergeTreeOp): SegmentGroup | SegmentGroup[] { + // eslint-disable-next-line import/no-deprecated let metadata: SegmentGroup | SegmentGroup[] | undefined; const stashed = true; switch (op.type) { @@ -862,6 +921,10 @@ export class Client extends TypedEventEmitter { this.applyAnnotateRangeOp({ op, stashed }); metadata = this.peekPendingSegmentGroups(); break; + case MergeTreeDeltaType.OBLITERATE: + this.applyObliterateRangeOp({ op }); + metadata = this.peekPendingSegmentGroups(); + break; case MergeTreeDeltaType.GROUP: return op.ops.map((o) => this.applyStashedOp(o)); default: @@ -890,7 +953,7 @@ export class Client extends TypedEventEmitter { this.updateSeqNumbers(msg.minimumSequenceNumber, msg.sequenceNumber); } - public updateSeqNumbers(min: number, seq: number) { + private updateSeqNumbers(min: number, seq: number) { const collabWindow = this.getCollabWindow(); // Equal is fine here due to SharedSegmentSequence<>.snapshotContent() potentially updating with same # assert( @@ -925,22 +988,27 @@ export class Client extends TypedEventEmitter { private lastNormalizationRefSeq = 0; - private pendingRebase: List | undefined; + // eslint-disable-next-line import/no-deprecated + private pendingRebase: DoublyLinkedList | undefined; + /** - * Given an pending operation and segment group, regenerate the op, so it + * Given a pending operation and segment group, regenerate the op, so it * can be resubmitted * @param resetOp - The op to reset * @param segmentGroup - The segment group associated with the op */ public regeneratePendingOp( resetOp: IMergeTreeOp, + // eslint-disable-next-line import/no-deprecated 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) { // sometimes we rebase to an empty op + // eslint-disable-next-line import/no-deprecated return createGroupOp(); } firstGroup = segmentGroup[0]; @@ -995,9 +1063,11 @@ export class Client extends TypedEventEmitter { ); opList.push(...this.resetPendingDeltaToOps(resetOp, segmentGroup)); } + // eslint-disable-next-line import/no-deprecated return opList.length === 1 ? opList[0] : createGroupOp(...opList); } + // eslint-disable-next-line import/no-deprecated public createTextHelper(): IMergeTreeTextHelper { return new MergeTreeTextHelper(this._mergeTree); } @@ -1053,22 +1123,12 @@ export class Client extends TypedEventEmitter { return loader.initialize(storage); } - /** - * @deprecated this functionality is no longer supported and will be removed - */ - getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap { - return this._mergeTree.getStackContext( - startPos, - this.getCollabWindow().clientId, - rangeLabels, - ); - } - private getLocalSequenceNumber() { const segWindow = this.getCollabWindow(); return segWindow.collaborating ? UnassignedSequenceNumber : UniversalSequenceNumber; } + // eslint-disable-next-line import/no-deprecated localTransaction(groupOp: IMergeTreeGroupMsg) { for (const op of groupOp.ops) { const opArgs: IMergeTreeDeltaOpArgs = { @@ -1085,21 +1145,14 @@ export class Client extends TypedEventEmitter { case MergeTreeDeltaType.REMOVE: this.applyRemoveRangeOp(opArgs); break; + case MergeTreeDeltaType.OBLITERATE: + this.applyObliterateRangeOp(opArgs); + break; default: break; } } } - updateConsensusProperty(op: IMergeTreeAnnotateMsg, msg: ISequencedDocumentMessage) { - const markerId = op.relativePos1!.id!; - const consensusInfo = this.pendingConsensus.get(markerId); - if (consensusInfo) { - consensusInfo.marker.addProperties(op.props, op.combiningOp, msg.sequenceNumber); - } - this._mergeTree.addMinSeqListener(msg.sequenceNumber, () => - consensusInfo!.callback(consensusInfo!.marker), - ); - } updateMinSeq(minSeq: number) { this._mergeTree.setMinSeq(minSeq); @@ -1129,6 +1182,7 @@ export class Client extends TypedEventEmitter { } return propertiesAtPosition; } + getRangeExtentsOfPosition(pos: number) { let posStart: number | undefined; let posAfterEnd: number | undefined; @@ -1141,9 +1195,11 @@ export class Client extends TypedEventEmitter { } return { posStart, posAfterEnd }; } + getCurrentSeq() { return this.getCollabWindow().currentSeq; } + getClientId() { return this.getCollabWindow().clientId; } diff --git a/packages/dds/merge-tree/src/collections/heap.ts b/packages/dds/merge-tree/src/collections/heap.ts deleted file mode 100644 index ccdca2b7d673..000000000000 --- a/packages/dds/merge-tree/src/collections/heap.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export interface Comparer { - compare(a: T, b: T): number; - min: T; -} - -export class Heap { - private L: T[]; - public count() { - return this.L.length - 1; - } - constructor( - a: T[], - public comp: Comparer, - ) { - this.L = [comp.min]; - for (let i = 0, len = a.length; i < len; i++) { - this.add(a[i]); - } - } - public peek() { - return this.L[1]; - } - - public get() { - const x = this.L[1]; - this.L[1] = this.L[this.count()]; - this.L.pop(); - this.fixDown(1); - return x; - } - - public add(x: T) { - this.L.push(x); - this.fixup(this.count()); - } - - /* eslint-disable no-bitwise */ - private fixup(k: number) { - let _k = k; - while (_k > 1 && this.comp.compare(this.L[_k >> 1], this.L[_k]) > 0) { - const tmp = this.L[_k >> 1]; - this.L[_k >> 1] = this.L[_k]; - this.L[_k] = tmp; - _k = _k >> 1; - } - } - - private fixDown(k: number) { - let _k = k; - while (_k << 1 <= this.count()) { - let j = _k << 1; - if (j < this.count() && this.comp.compare(this.L[j], this.L[j + 1]) > 0) { - j++; - } - if (this.comp.compare(this.L[_k], this.L[j]) <= 0) { - break; - } - const tmp = this.L[_k]; - this.L[_k] = this.L[j]; - this.L[j] = tmp; - _k = j; - } - } - /* eslint-enable no-bitwise */ -} diff --git a/packages/dds/merge-tree/src/collections/index.ts b/packages/dds/merge-tree/src/collections/index.ts index 4c94b2dae59f..64235c8c0a7b 100644 --- a/packages/dds/merge-tree/src/collections/index.ts +++ b/packages/dds/merge-tree/src/collections/index.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. */ -export { Comparer, Heap } from "./heap"; -export { List, ListNode, ListNodeRange, walkList } from "./list"; +export { DoublyLinkedList, ListNode, ListNodeRange, walkList } from "./list"; export { ConflictAction, Dictionary, @@ -20,4 +19,3 @@ export { RedBlackTree, SortedDictionary, } from "./rbTree"; -export { Stack } from "./stack"; diff --git a/packages/dds/merge-tree/src/collections/list.ts b/packages/dds/merge-tree/src/collections/list.ts index 1f42a38266a3..ef44efd3fd91 100644 --- a/packages/dds/merge-tree/src/collections/list.ts +++ b/packages/dds/merge-tree/src/collections/list.ts @@ -6,7 +6,7 @@ import { UsageError } from "@fluidframework/telemetry-utils"; export interface ListNode { - readonly list: List | undefined; + readonly list: DoublyLinkedList | undefined; readonly data: T; readonly next: ListNode | undefined; readonly prev: ListNode | undefined; @@ -21,8 +21,8 @@ class HeadNode { public _next: HeadNode | DataNode = this; public _prev: HeadNode | DataNode = this; public headNode: HeadNode = this; - private readonly _list?: List; - constructor(list: List | undefined) { + private readonly _list?: DoublyLinkedList; + constructor(list: DoublyLinkedList | undefined) { if (list) { this._list = list; } @@ -78,14 +78,16 @@ function insertAfter(node: DataNode | HeadNode, items: T[]): ListNodeRa return newRange; } -export class List +export class DoublyLinkedList implements Iterable>, Partial>, // try to match array signature and semantics where possible Pick[], "pop" | "shift" | "length" | "includes"> { - find(predicate: (value: ListNode, obj: List) => unknown): ListNode | undefined { + find( + predicate: (value: ListNode, obj: DoublyLinkedList) => unknown, + ): ListNode | undefined { let found: ListNode | undefined; walkList(this, (node) => { if (predicate(node, this)) { @@ -132,17 +134,28 @@ export class List return insertAfter(start, items); } + /** + * Remove and return the first element + */ shift(): ListNode | undefined { return this.remove(this.first); } + /** + * Insert `items` at start of list + */ unshift(...items: T[]): ListNodeRange { this._len += items.length; return insertAfter(this.headNode, items); } - splice(start: ListNode, countOrEnd?: ListNode | number): List { - const newList = new List(); + /** + * Remove nodes starting at `start` until either the `end` node is reached + * or until `count` nodes have been removed. Returns the removed nodes as + * a separate linked list + */ + splice(start: ListNode, countOrEnd?: ListNode | number): DoublyLinkedList { + const newList = new DoublyLinkedList(); walkList( this, (node) => { @@ -225,7 +238,7 @@ export class List } export function walkList( - list: List, + list: DoublyLinkedList, visitor: (node: ListNode) => boolean | void, start?: ListNode, forward: boolean = true, diff --git a/packages/dds/merge-tree/src/collections/rbTree.ts b/packages/dds/merge-tree/src/collections/rbTree.ts index 6f73c9162f8b..67ba6b5a0784 100644 --- a/packages/dds/merge-tree/src/collections/rbTree.ts +++ b/packages/dds/merge-tree/src/collections/rbTree.ts @@ -3,10 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ - -/* Remove once strictNullCheck is enabled */ - /** * @internal */ @@ -132,8 +128,8 @@ export class RedBlackTree implements SortedDictionary private readonly aug?: IRBAugmentation, ) {} - private makeNode(key: TKey, data: TData, color: RBColor, size: number) { - return >{ key, data, color, size }; + private makeNode(key: TKey, data: TData, color: RBColor, size: number): RBNode { + return { key, data, color, size } as any as RBNode; } private isRed(node: RBNode | undefined) { @@ -609,8 +605,8 @@ export class RedBlackTree implements SortedDictionary } public keys() { - const keyList = []; - const actions = >{ + const keyList: TKey[] = []; + const actions: RBNodeActions = { showStructure: true, infix: (node) => { keyList.push(node.key); @@ -641,8 +637,7 @@ export class RedBlackTree implements SortedDictionary let go = true; if (node) { if (actions.pre) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.pre(node); } } @@ -650,8 +645,7 @@ export class RedBlackTree implements SortedDictionary go = this.nodeWalk(node.left, actions); } if (go && actions.infix) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.infix(node); } } @@ -659,8 +653,7 @@ export class RedBlackTree implements SortedDictionary go = this.nodeWalk(node.right, actions); } if (go && actions.post) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.post(node); } } @@ -675,8 +668,7 @@ export class RedBlackTree implements SortedDictionary let go = true; if (node) { if (actions.pre) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.pre(node); } } @@ -684,8 +676,7 @@ export class RedBlackTree implements SortedDictionary go = this.nodeWalkBackward(node.right, actions); } if (go && actions.infix) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.infix(node); } } @@ -693,8 +684,7 @@ export class RedBlackTree implements SortedDictionary go = this.nodeWalkBackward(node.left, actions); } if (go && actions.post) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if (actions.showStructure || node.color === RBColor.BLACK) { + if (!!actions.showStructure || node.color === RBColor.BLACK) { go = actions.post(node); } } diff --git a/packages/dds/merge-tree/src/collections/stack.ts b/packages/dds/merge-tree/src/collections/stack.ts deleted file mode 100644 index 246092c52e2d..000000000000 --- a/packages/dds/merge-tree/src/collections/stack.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * @deprecated This functionality was not intended for public export and will - * be removed in a future release. - * @alpha - */ -export class Stack { - public items: T[] = []; - public push(val: T) { - this.items.push(val); - } - - public empty() { - return this.items.length === 0; - } - - public top(): T | undefined { - return this.items[this.items.length - 1]; - } - - public pop(): T | undefined { - return this.items.pop(); - } -} diff --git a/packages/dds/merge-tree/src/constants.ts b/packages/dds/merge-tree/src/constants.ts index 92f4ac2bce51..ddbd0e21b41a 100644 --- a/packages/dds/merge-tree/src/constants.ts +++ b/packages/dds/merge-tree/src/constants.ts @@ -4,34 +4,33 @@ */ /** - * Sequence numbers for shared segments start at 1 or greater. Every segment marked - * with sequence number zero will be counted as part of the requested string. + * The sequence number which can be seen by all ops. + * + * This is useful in the context of snapshot loading, rollback, among other + * scenarios. * - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const UniversalSequenceNumber = 0; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * The sequence number of an op before it is acked. + * * @internal */ export const UnassignedSequenceNumber = -1; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const TreeMaintenanceSequenceNumber = -2; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const LocalClientId = -1; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const NonCollabClient = -2; diff --git a/packages/dds/merge-tree/src/endOfTreeSegment.ts b/packages/dds/merge-tree/src/endOfTreeSegment.ts index 5b7c4ebacd3f..8491865d4d41 100644 --- a/packages/dds/merge-tree/src/endOfTreeSegment.ts +++ b/packages/dds/merge-tree/src/endOfTreeSegment.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { LocalClientId } from "./constants"; import { LocalReferenceCollection } from "./localReference"; diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index 12c18f95f77f..d38895b4cf62 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -11,7 +11,6 @@ export { SequenceOffsets, } from "./attributionCollection"; export { createInsertOnlyAttributionPolicy } from "./attributionPolicy"; -export { IIntegerRange } from "./base"; export { Client, IClientEvents } from "./client"; export { ConflictAction, @@ -27,15 +26,8 @@ export { RBNodeActions, RedBlackTree, SortedDictionary, - Stack, } from "./collections"; -export { - LocalClientId, - NonCollabClient, - TreeMaintenanceSequenceNumber, - UnassignedSequenceNumber, - UniversalSequenceNumber, -} from "./constants"; +export { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; export { createDetachedLocalReferencePosition, LocalReferenceCollection, @@ -54,23 +46,17 @@ export { IMergeTreeDeltaOpArgs, IMergeTreeMaintenanceCallbackArgs, IMergeTreeSegmentDelta, - MergeTreeDeltaCallback, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, - MergeTreeMaintenanceCallback, MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback"; export { BaseSegment, CollaborationWindow, - compareNumbers, - compareStrings, debugMarkerToString, - IConsensusInfo, IJSONMarkerSegment, - IMarkerModifiedAction, IMergeNodeCommon, - internedSpaces, + IMoveInfo, IRemovalInfo, ISegment, ISegmentAction, @@ -78,7 +64,6 @@ export { MergeNode, reservedMarkerIdKey, reservedMarkerSimpleTypeKey, - SegmentAccumulator, SegmentGroup, toRemovalInfo, } from "./mergeTreeNodes"; @@ -89,15 +74,14 @@ export { TrackingGroupCollection, } from "./mergeTreeTracking"; export { - createAnnotateMarkerOp, createAnnotateRangeOp, createGroupOp, createInsertOp, createInsertSegmentOp, createRemoveRangeOp, + createObliterateRangeOp, } from "./opBuilder"; export { - ICombiningOp, IJSONSegment, IMarkerDef, IMergeTreeAnnotateMsg, @@ -110,30 +94,16 @@ export { IRelativePosition, MergeTreeDeltaType, ReferenceType, + IMergeTreeObliterateMsg, } from "./ops"; -export { - addProperties, - clone, - combine, - createMap, - extend, - extendIfUndefined, - IConsensusValue, - MapLike, - matchProperties, - PropertySet, -} from "./properties"; +export { addProperties, createMap, MapLike, matchProperties, PropertySet } from "./properties"; export { compareReferencePositions, DetachedReferencePosition, maxReferencePosition, minReferencePosition, - RangeStackMap, ReferencePosition, - refGetRangeLabels, refGetTileLabels, - refHasRangeLabel, - refHasRangeLabels, refHasTileLabel, refHasTileLabels, refTypeIncludesFlag, diff --git a/packages/dds/merge-tree/src/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index 9a0522057801..2f7fe9bad326 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -3,21 +3,15 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { UsageError } from "@fluidframework/telemetry-utils"; -import { List, ListNode, walkList } from "./collections"; +import { DoublyLinkedList, ListNode, walkList } from "./collections"; import { ISegment } from "./mergeTreeNodes"; import { TrackingGroup, TrackingGroupCollection } from "./mergeTreeTracking"; -import { ICombiningOp, ReferenceType } from "./ops"; +import { ReferenceType } from "./ops"; +// eslint-disable-next-line import/no-deprecated import { addProperties, PropertySet } from "./properties"; -import { - refHasTileLabels, - refHasRangeLabels, - ReferencePosition, - refTypeIncludesFlag, -} from "./referencePositions"; +import { ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; /** * Dictates the preferential direction for a {@link ReferencePosition} to slide @@ -42,9 +36,6 @@ export const SlidingPreference = { */ export type SlidingPreference = (typeof SlidingPreference)[keyof typeof SlidingPreference]; -/** - * @internal - */ function _validateReferenceType(refType: ReferenceType) { let exclusiveCount = 0; if (refTypeIncludesFlag(refType, ReferenceType.Transient)) { @@ -135,8 +126,9 @@ class LocalReference implements LocalReferencePosition { return false; } - public addProperties(newProps: PropertySet, op?: ICombiningOp) { - this.properties = addProperties(this.properties, newProps, op); + public addProperties(newProps: PropertySet) { + // eslint-disable-next-line import/no-deprecated + this.properties = addProperties(this.properties, newProps); } public getSegment() { @@ -166,9 +158,9 @@ export function createDetachedLocalReferencePosition( } interface IRefsAtOffset { - before?: List; - at?: List; - after?: List; + before?: DoublyLinkedList; + at?: DoublyLinkedList; + after?: DoublyLinkedList; } function assertLocalReferences(lref: any): asserts lref is LocalReference { @@ -207,6 +199,18 @@ export function* filterLocalReferencePositions( } /** + * Injectable hook for validating that the refCount property matches the + * expected value + */ +let validateRefCount: ((collection?: LocalReferenceCollection) => void) | undefined; + +export function setValidateRefCount(cb?: (collection?: LocalReferenceCollection) => void) { + validateRefCount = cb; +} + +/** + * Represents a collection of {@link LocalReferencePosition}s associated with + * one segment in a merge-tree. * Represents a collection of {@link LocalReferencePosition}s associated with one segment in a merge-tree. * @alpha */ @@ -226,12 +230,10 @@ export class LocalReferenceCollection { // segments that had no local references. Account for them now by padding the array. seg1.localRefs.refsByOffset.length += seg2.cachedLength; } + validateRefCount?.(seg1.localRefs); + validateRefCount?.(seg2.localRefs); } - /** - * @remarks This method should only be called by mergeTree. - */ - public hierRefCount: number = 0; private readonly refsByOffset: (IRefsAtOffset | undefined)[]; private refCount: number = 0; @@ -286,34 +288,11 @@ export class LocalReferenceCollection { return iterator; } - /** - * @remarks This method should only be called by mergeTree. - */ - public clear() { - this.refCount = 0; - this.hierRefCount = 0; - const detachSegments = (refs: List | undefined) => { - if (refs) { - for (const r of refs) { - this.removeLocalRef(r.data); - } - } - }; - for (let i = 0; i < this.refsByOffset.length; i++) { - const refsAtOffset = this.refsByOffset[i]; - if (refsAtOffset) { - detachSegments(refsAtOffset.before); - detachSegments(refsAtOffset.at); - detachSegments(refsAtOffset.after); - this.refsByOffset[i] = undefined; - } - } - } - /** * @remarks This method should only be called by mergeTree. */ public get empty() { + validateRefCount?.(this); return this.refCount === 0; } @@ -332,6 +311,7 @@ export class LocalReferenceCollection { if (!refTypeIncludesFlag(ref, ReferenceType.Transient)) { this.addLocalRef(ref, offset); } + validateRefCount?.(this); return ref; } @@ -348,17 +328,15 @@ export class LocalReferenceCollection { lref.link(this.segment, offset, undefined); } else { const refsAtOffset = (this.refsByOffset[offset] = this.refsByOffset[offset] ?? { - at: new List(), + at: new DoublyLinkedList(), }); - const atRefs = (refsAtOffset.at = refsAtOffset.at ?? new List()); + const atRefs = (refsAtOffset.at = refsAtOffset.at ?? new DoublyLinkedList()); lref.link(this.segment, offset, atRefs.push(lref).last); - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; } + validateRefCount?.(this); } /** @@ -371,11 +349,10 @@ export class LocalReferenceCollection { const node = lref.getListNode(); node?.list?.remove(node); - lref.link(lref.getSegment(), lref.getOffset(), undefined); - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { - this.hierRefCount--; - } + lref.link(undefined, 0, undefined); + this.refCount--; + validateRefCount?.(this); return lref; } } @@ -395,9 +372,7 @@ export class LocalReferenceCollection { if (!other || other.empty) { return; } - this.hierRefCount += other.hierRefCount; this.refCount += other.refCount; - other.hierRefCount = 0; other.refCount = 0; for (const lref of other) { assertLocalReferences(lref); @@ -409,6 +384,7 @@ export class LocalReferenceCollection { } this.refsByOffset.push(...other.refsByOffset); + other.refsByOffset.length = 0; } /** * Returns true of the local reference is in the collection, otherwise false. @@ -435,11 +411,9 @@ export class LocalReferenceCollection { const offset = lref.getOffset(); const refsAtOffset = this.refsByOffset[offset]; if ( - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - refsAtOffset?.before?.includes(listNode) || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - refsAtOffset?.at?.includes(listNode) || - refsAtOffset?.after?.includes(listNode) + !!refsAtOffset?.before?.includes(listNode) || + !!refsAtOffset?.at?.includes(listNode) || + !!refsAtOffset?.after?.includes(listNode) ) { return true; } @@ -468,10 +442,6 @@ export class LocalReferenceCollection { for (const lref of localRefs) { assertLocalReferences(lref); lref.link(splitSeg, lref.getOffset() - offset, lref.getListNode()); - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { - this.hierRefCount--; - localRefs.hierRefCount++; - } this.refCount--; localRefs.refCount++; } @@ -479,13 +449,14 @@ export class LocalReferenceCollection { // shrink the offset array when empty and splitting this.refsByOffset.length = offset; } + validateRefCount?.(this); } /** * @remarks This method should only be called by mergeTree. */ public addBeforeTombstones(...refs: Iterable[]) { - const beforeRefs = this.refsByOffset[0]?.before ?? new List(); + const beforeRefs = this.refsByOffset[0]?.before ?? new DoublyLinkedList(); if (this.refsByOffset[0]?.before === undefined) { const refsAtOffset = (this.refsByOffset[0] ??= { before: beforeRefs }); @@ -505,9 +476,6 @@ export class LocalReferenceCollection { ? beforeRefs.unshift(lref)?.first : beforeRefs.insertAfter(precedingRef, lref)?.first; lref.link(this.segment, 0, precedingRef); - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; lref.callbacks?.afterSlide?.(lref); } else { @@ -515,13 +483,14 @@ export class LocalReferenceCollection { } } } + validateRefCount?.(this); } /** * @remarks This method should only be called by mergeTree. */ public addAfterTombstones(...refs: Iterable[]) { const lastOffset = this.segment.cachedLength - 1; - const afterRefs = this.refsByOffset[lastOffset]?.after ?? new List(); + const afterRefs = this.refsByOffset[lastOffset]?.after ?? new DoublyLinkedList(); if (this.refsByOffset[lastOffset]?.after === undefined) { const refsAtOffset = (this.refsByOffset[lastOffset] ??= { after: afterRefs }); @@ -537,9 +506,6 @@ export class LocalReferenceCollection { lref.callbacks?.beforeSlide?.(lref); afterRefs.push(lref); lref.link(this.segment, lastOffset, afterRefs.last); - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; lref.callbacks?.afterSlide?.(lref); } else { @@ -547,6 +513,7 @@ export class LocalReferenceCollection { } } } + validateRefCount?.(this); } /** @@ -577,7 +544,8 @@ export class LocalReferenceCollection { } let offset = start?.getOffset() ?? (forward ? 0 : this.segment.cachedLength - 1); - const offsetPositions: List = new List(); + const offsetPositions: DoublyLinkedList = + new DoublyLinkedList(); offsetPositions.push( this.refsByOffset[offset]?.before, this.refsByOffset[offset]?.at, @@ -599,7 +567,7 @@ export class LocalReferenceCollection { } } - const listWalker = (pos: List) => { + const listWalker = (pos: DoublyLinkedList) => { return walkList( pos, (node) => visitor(node.data), diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 3be0c3c0184e..d2f93072929e 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -4,16 +4,13 @@ */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ -/* eslint-disable import/no-deprecated */ -/* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */ +/* eslint-disable no-bitwise */ -import { assert } from "@fluidframework/core-utils"; +import { assert, Heap, IComparer } from "@fluidframework/core-utils"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils"; import { IAttributionCollectionSerializer } from "./attributionCollection"; -import { Comparer, Heap, List, ListNode, Stack } from "./collections"; +import { DoublyLinkedList, ListNode } from "./collections"; import { - LocalClientId, NonCollabClient, TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, @@ -27,17 +24,15 @@ import { SlidingPreference, } from "./localReference"; import { - BaseSegment, BlockAction, + // eslint-disable-next-line import/no-deprecated CollaborationWindow, IHierBlock, IMergeBlock, IMergeLeaf, IMergeNode, - IncrementalExecOp, - IncrementalMapState, + IMoveInfo, InsertContext, - internedSpaces, IRemovalInfo, ISegment, ISegmentAction, @@ -45,10 +40,11 @@ import { Marker, MaxNodesInBlock, MergeBlock, - MinListener, reservedMarkerIdKey, SegmentActions, + // eslint-disable-next-line import/no-deprecated SegmentGroup, + toMoveInfo, seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; @@ -60,22 +56,14 @@ import { MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback"; import { createAnnotateRangeOp, createInsertSegmentOp, createRemoveRangeOp } from "./opBuilder"; -import { - ICombiningOp, - IMergeTreeDeltaOp, - IRelativePosition, - MergeTreeDeltaType, - ReferenceType, -} from "./ops"; +import { IMergeTreeDeltaOp, IRelativePosition, MergeTreeDeltaType, ReferenceType } from "./ops"; import { PartialSequenceLengths } from "./partialLengths"; -import { createMap, extend, MapLike, PropertySet } from "./properties"; +// eslint-disable-next-line import/no-deprecated +import { createMap, extend, extendIfUndefined, MapLike, PropertySet } from "./properties"; import { refTypeIncludesFlag, ReferencePosition, DetachedReferencePosition, - RangeStackMap, - refHasRangeLabel, - refGetRangeLabels, refGetTileLabels, refHasTileLabel, } from "./referencePositions"; @@ -89,6 +77,7 @@ import { } from "./mergeTreeNodeWalk"; import type { TrackingGroup } from "./mergeTreeTracking"; import { zamboniSegments } from "./zamboni"; +// eslint-disable-next-line import/no-deprecated import { Client } from "./client"; import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment"; @@ -98,15 +87,25 @@ import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment"; */ export type ISegmentLeaf = ISegment & IMergeLeaf; -const minListenerComparer: Comparer = { - min: { - minRequired: Number.MIN_VALUE, - onMinGE: () => { - assert(false, 0x048 /* "onMinGE()" */); - }, - }, - compare: (a, b) => a.minRequired - b.minRequired, -}; +function wasRemovedAfter(seg: ISegment, seq: number): boolean { + return ( + seg.removedSeq !== UnassignedSequenceNumber && + (seg.removedSeq === undefined || seg.removedSeq > seq) + ); +} + +function markSegmentMoved(seg: ISegment, moveInfo: IMoveInfo): void { + seg.moveDst = moveInfo.moveDst; + seg.movedClientIds = moveInfo.movedClientIds.slice(); + seg.movedSeqs = [moveInfo.movedSeq]; + seg.movedSeq = moveInfo.movedSeq; + seg.localMovedSeq = moveInfo.localMovedSeq; + seg.wasMovedOnInsert = moveInfo.wasMovedOnInsert; +} + +function isMoved(segment: ISegment): boolean { + return toMoveInfo(segment) !== undefined; +} function isRemoved(segment: ISegment): boolean { return toRemovalInfo(segment) !== undefined; @@ -117,6 +116,15 @@ function isRemovedAndAcked(segment: ISegment): segment is ISegment & IRemovalInf return removalInfo !== undefined && removalInfo.removedSeq !== UnassignedSequenceNumber; } +function isMovedAndAcked(segment: ISegment): boolean { + const moveInfo = toMoveInfo(segment); + return moveInfo !== undefined && moveInfo.movedSeq !== UnassignedSequenceNumber; +} + +function isRemovedAndAckedOrMovedAndAcked(segment: ISegment): boolean { + return isRemovedAndAcked(segment) || isMovedAndAcked(segment); +} + function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode): number | undefined { if (!node.isLeaf()) { return node.cachedLength; @@ -124,7 +132,7 @@ function nodeTotalLength(mergeTree: MergeTree, node: IMergeNode): number | undef return mergeTree.localNetLength(node); } -const LRUSegmentComparer: Comparer = { +const LRUSegmentComparer: IComparer = { min: { maxSeq: -2 }, compare: (a, b) => a.maxSeq - b.maxSeq, }; @@ -136,65 +144,6 @@ interface IReferenceSearchInfo { tile?: ReferencePosition; } -interface IMarkerSearchRangeInfo { - mergeTree: MergeTree; - rangeLabels: string[]; - stacks: RangeStackMap; -} - -function applyLeafRangeMarker(marker: Marker, searchInfo: IMarkerSearchRangeInfo) { - for (const rangeLabel of searchInfo.rangeLabels) { - if (refHasRangeLabel(marker, rangeLabel)) { - let currentStack = searchInfo.stacks[rangeLabel]; - if (currentStack === undefined) { - currentStack = new Stack(); - searchInfo.stacks[rangeLabel] = currentStack; - } - applyRangeReference(currentStack, marker); - } - } -} - -function recordRangeLeaf( - segment: ISegment, - segpos: number, - refSeq: number, - clientId: number, - start: number | undefined, - end: number | undefined, - searchInfo: IMarkerSearchRangeInfo, -) { - if (Marker.is(segment)) { - if (segment.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - applyLeafRangeMarker(segment, searchInfo); - } - } - return false; -} - -function rangeShift( - node: IMergeNode, - segpos: number, - refSeq: number, - clientId: number, - offset: number | undefined, - end: number | undefined, - searchInfo: IMarkerSearchRangeInfo, -) { - if (node.isLeaf()) { - const seg = node; - if ((searchInfo.mergeTree.localNetLength(seg) ?? 0) > 0 && Marker.is(seg)) { - if (seg.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - applyLeafRangeMarker(seg, searchInfo); - } - } - } else { - const block = node; - applyStackDelta(searchInfo.stacks, block.rangeStacks); - } - return true; -} - function recordTileStart( segment: ISegment, segpos: number, @@ -229,18 +178,19 @@ function tileShift( } } } else { - const block = node; + const block = node as IHierBlock; const marker = searchInfo.tilePrecedesPos - ? block.rightmostTiles[searchInfo.tileLabel] - : block.leftmostTiles[searchInfo.tileLabel]; + ? block.rightmostTiles[searchInfo.tileLabel] + : block.leftmostTiles[searchInfo.tileLabel]; if (marker !== undefined) { + assert(marker.isLeaf() && Marker.is(marker), "Object returned is not a valid marker"); searchInfo.tile = marker; } } return true; } -function addTile(tile: ReferencePosition, tiles: object) { +function addTile(tile: ReferencePosition, tiles: MapLike) { const tileLabels = refGetTileLabels(tile); if (tileLabels) { for (const tileLabel of tileLabels) { @@ -249,7 +199,7 @@ function addTile(tile: ReferencePosition, tiles: object) { } } -function addTileIfNotPresent(tile: ReferencePosition, tiles: object) { +function addTileIfNotPresent(tile: ReferencePosition, tiles: MapLike) { const tileLabels = refGetTileLabels(tile); if (tileLabels) { for (const tileLabel of tileLabels) { @@ -259,168 +209,26 @@ function addTileIfNotPresent(tile: ReferencePosition, tiles: object) { } } } -function applyStackDelta(currentStackMap: RangeStackMap, deltaStackMap: RangeStackMap) { - // eslint-disable-next-line guard-for-in, no-restricted-syntax - for (const label in deltaStackMap) { - const deltaStack = deltaStackMap[label]; - if (!deltaStack.empty()) { - let currentStack = currentStackMap[label]; - if (currentStack === undefined) { - currentStack = new Stack(); - currentStackMap[label] = currentStack; - } - for (const delta of deltaStack.items) { - applyRangeReference(currentStack, delta); - } - } - } -} -function applyRangeReference(stack: Stack, delta: ReferencePosition) { - if (refTypeIncludesFlag(delta, ReferenceType.NestBegin)) { - stack.push(delta); - return true; - } else { - // Assume delta is end reference - const top = stack.top(); - // TODO: match end with begin - if (top && refTypeIncludesFlag(top, ReferenceType.NestBegin)) { - stack.pop(); - } else { - stack.push(delta); - } - return false; - } -} - -/** - * Reference types which have special bookkeeping within the merge tree (in {@link HierMergeBlock}s) - * and thus require updating path lengths when changed. - * - * TODO:AB#4069: This functionality is old and not well-tested. It's not clear how much of it is needed-- - * we should better test the parts that are necessary and remove the rest. - */ -const hierRefTypes = ReferenceType.NestBegin | ReferenceType.NestEnd | ReferenceType.Tile; -function addNodeReferences( - mergeTree: MergeTree, - node: IMergeNode, - rightmostTiles: MapLike, - leftmostTiles: MapLike, - rangeStacks: RangeStackMap, -) { - function updateRangeInfo(label: string, refPos: ReferencePosition) { - let stack = rangeStacks[label]; - if (stack === undefined) { - stack = new Stack(); - rangeStacks[label] = stack; - } - applyRangeReference(stack, refPos); - } - if (node.isLeaf()) { - const segment = node; - if ((mergeTree.localNetLength(segment) ?? 0) > 0) { - if (Marker.is(segment)) { - const markerId = segment.getId(); - // Also in insertMarker but need for reload segs case - // can add option for this only from reload segs - if (markerId) { - mergeTree.mapIdToSegment(markerId, segment); - } - if (refTypeIncludesFlag(segment, ReferenceType.Tile)) { - addTile(segment, rightmostTiles); - addTileIfNotPresent(segment, leftmostTiles); - } - if (segment.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - const rangeLabels = refGetRangeLabels(segment); - if (rangeLabels) { - for (const label of rangeLabels) { - updateRangeInfo(label, segment); - } - } - } - } else { - const baseSegment = node as BaseSegment; - if ( - baseSegment.localRefs && - baseSegment.localRefs.hierRefCount !== undefined && - baseSegment.localRefs.hierRefCount > 0 - ) { - for (const lref of baseSegment.localRefs) { - if (refTypeIncludesFlag(lref, ReferenceType.Tile)) { - addTile(lref, rightmostTiles); - addTileIfNotPresent(lref, leftmostTiles); - } - if (lref.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - for (const label of refGetRangeLabels(lref)!) { - updateRangeInfo(label, lref); - } - } - } - } - } - } - } else { - const block = node; - applyStackDelta(rangeStacks, block.rangeStacks); - extend(rightmostTiles, block.rightmostTiles); - extendIfUndefined(leftmostTiles, block.leftmostTiles); - } -} - -function extendIfUndefined(base: MapLike, extension: MapLike | undefined) { - if (extension !== undefined) { - // eslint-disable-next-line no-restricted-syntax - for (const key in extension) { - if (base[key] === undefined) { - base[key] = extension[key]; - } - } - } - return base; -} class HierMergeBlock extends MergeBlock implements IHierBlock { public rightmostTiles: MapLike; public leftmostTiles: MapLike; - public rangeStacks: MapLike>; constructor(childCount: number) { super(childCount); + // eslint-disable-next-line import/no-deprecated this.rightmostTiles = createMap(); + // eslint-disable-next-line import/no-deprecated this.leftmostTiles = createMap(); - this.rangeStacks = createMap>(); } public hierBlock() { return this; } - - public hierToString(indentCount: number) { - let strbuf = ""; - // eslint-disable-next-line guard-for-in, no-restricted-syntax - for (const key in this.rangeStacks) { - const stack = this.rangeStacks[key]; - strbuf += internedSpaces(indentCount); - strbuf += `${key}: `; - for (const item of stack.items) { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - strbuf += `${item.toString()} `; - } - strbuf += "\n"; - } - return strbuf; - } -} - -/** - * @internal - */ -export interface ClientSeq { - refSeq: number; - clientId: string; } /** - * @internal + * @alpha */ export interface IMergeTreeOptions { catchUpBlobName?: string; @@ -456,10 +264,20 @@ export interface IMergeTreeOptions { * Options related to attribution */ attribution?: IMergeTreeAttributionOptions; + + /** + * Enables support for the obliterate operation -- a stronger form of remove + * which deletes concurrently inserted segments + * + * Obliterate is currently experimental and may not work in all scenarios. + * + * Default value: false + */ + mergeTreeEnableObliterate?: boolean; } /** - * @internal + * @alpha */ export interface IMergeTreeAttributionOptions { /** @@ -485,7 +303,7 @@ export interface IMergeTreeAttributionOptions { /** * Implements policy dictating which kinds of operations should be attributed and how. * @sealed - * @internal + * @alpha */ export interface AttributionPolicy { /** @@ -495,6 +313,7 @@ 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. @@ -508,14 +327,6 @@ export interface AttributionPolicy { serializer: IAttributionCollectionSerializer; } -/** - * @internal - */ -export const clientSeqComparer: Comparer = { - min: { refSeq: -1, clientId: "" }, - compare: (a, b) => a.refSeq - b.refSeq, -}; - /** * @internal */ @@ -561,7 +372,11 @@ function getSlideToSegment( cache?: Map, useNewSlidingBehavior: boolean = false, ): [ISegment | undefined, "start" | "end" | undefined] { - if (!segment || !isRemovedAndAcked(segment) || segment.endpointType !== undefined) { + if ( + !segment || + !isRemovedAndAckedOrMovedAndAcked(segment) || + segment.endpointType !== undefined + ) { return [segment, undefined]; } @@ -571,12 +386,15 @@ function getSlideToSegment( } const result: { seg?: ISegment } = {}; cache?.set(segment, result); - const goFurtherToFindSlideToSegment = (seg) => { - if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) { + const goFurtherToFindSlideToSegment = (seg: ISegment) => { + if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAckedOrMovedAndAcked(seg)) { result.seg = seg; return false; } - if (cache !== undefined && seg.removedSeq === segment.removedSeq) { + if ( + cache !== undefined && + (seg.removedSeq === segment.removedSeq || seg.movedSeq === segment.movedSeq) + ) { cache.set(seg, result); } return true; @@ -660,12 +478,15 @@ export class MergeTree { zamboniSegments: true, }; - private static readonly theUnfinishedNode = { childCount: -1 }; + private static readonly theUnfinishedNode = { childCount: -1 } as unknown as IMergeBlock; + // eslint-disable-next-line import/no-deprecated public readonly collabWindow = new CollaborationWindow(); - public readonly pendingSegments = new List(); - public readonly segmentsToScour = new Heap([], LRUSegmentComparer); + // eslint-disable-next-line import/no-deprecated + public readonly pendingSegments = new DoublyLinkedList(); + + public readonly segmentsToScour = new Heap(LRUSegmentComparer); public readonly attributionPolicy: AttributionPolicy | undefined; @@ -675,14 +496,45 @@ export class MergeTree { * This field enables tracking whether partials need to be recomputed using localSeq information. */ private localPartialsComputed = false; - // TODO: add remove on segment remove // for now assume only markers have ids and so point directly at the Segment // if we need to have pointers to non-markers, we can change to point at local refs - private readonly idToSegment = new Map(); - private minSeqListeners: Heap | undefined; + private readonly idToMarker = new Map(); public mergeTreeDeltaCallback?: MergeTreeDeltaCallback; public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback; + /** + * Array containing the sequence number of all move operations within the + * collab window + * + * When a segment is inserted, we must traverse to the left and right of it + * to determine whether the segment was inserted into an obliterated range. + * By keeping track of all move seqs, we can significantly reduce the search + * space we must traverse. + * + * Sequence numbers in `moveSeqs` are sorted to accelerate bookkeeping. + * + * See https://github.com/microsoft/FluidFramework/blob/main/packages/dds/merge-tree/docs/Obliterate.md#remote-perspective + * for additional context + */ + private moveSeqs: number[] = []; + + /** + * Similar to moveSeqs, but tracks local moves. These are not the move + * operations within the collab window, but rather local moves that have + * not been acked. + */ + private readonly localMoveSeqs: Set = new Set(); + + /** + * Groups of segments moved by local moves/obliterates + * + * When a local obliterate is acked, we must also ack segments that were + * concurrently obliterated on insert. We check this segment group to find + * such segments + */ + // eslint-disable-next-line import/no-deprecated + private readonly locallyMovedSegments: Map = new Map(); + public constructor(public options?: IMergeTreeOptions) { this._root = this.makeBlock(0); this._root.mergeTree = this; @@ -705,29 +557,6 @@ export class MergeTree { return block; } - public clone() { - const b = new MergeTree(this.options); - // For now assume that b will not collaborate - b.root = b.blockClone(this.root); - } - - public blockClone(block: IMergeBlock, segments?: ISegment[]) { - const bBlock = this.makeBlock(block.childCount); - for (let i = 0; i < block.childCount; i++) { - const child = block.children[i]; - if (child.isLeaf()) { - const segment = child.clone(); - bBlock.assignChild(segment, i); - segments?.push(segment); - } else { - bBlock.assignChild(this.blockClone(child, segments), i); - } - } - this.nodeUpdateLengthNewStructure(bBlock); - this.nodeUpdateOrdinals(bBlock); - return bBlock; - } - /** * Compute the net length of this segment from a local perspective. * @param segment - Segment whose length to find @@ -741,9 +570,13 @@ export class MergeTree { localSeq?: number, ): number | undefined { const removalInfo = toRemovalInfo(segment); + const moveInfo = toMoveInfo(segment); if (localSeq === undefined) { - if (removalInfo !== undefined) { - if (!seqLTE(removalInfo.removedSeq, this.collabWindow.minSeq)) { + if (removalInfo !== undefined || moveInfo !== undefined) { + if ( + (!!removalInfo && !seqLTE(removalInfo.removedSeq, this.collabWindow.minSeq)) || + (!!moveInfo && !seqLTE(moveInfo.movedSeq, this.collabWindow.minSeq)) + ) { return 0; } // this segment removed and outside the collab window which means it is zamboni eligible @@ -757,7 +590,7 @@ export class MergeTree { assert(refSeq !== undefined, 0x398 /* localSeq provided for local length without refSeq */); assert(segment.seq !== undefined, 0x399 /* segment with no seq in mergeTree */); - const { seq, removedSeq, localRemovedSeq } = segment; + const { seq, removedSeq, localRemovedSeq, movedSeq, localMovedSeq } = segment; if (seq !== UnassignedSequenceNumber) { // inserted remotely if ( @@ -765,7 +598,11 @@ export class MergeTree { (removedSeq !== undefined && removedSeq !== UnassignedSequenceNumber && removedSeq <= refSeq) || - (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) + (movedSeq !== undefined && + movedSeq !== UnassignedSequenceNumber && + movedSeq <= refSeq) || + (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) || + (localMovedSeq !== undefined && localMovedSeq <= localSeq) ) { return 0; } @@ -778,7 +615,8 @@ export class MergeTree { // inserted locally, still un-acked if ( segment.localSeq > localSeq || - (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) + (localRemovedSeq !== undefined && localRemovedSeq <= localSeq) || + (localMovedSeq !== undefined && localMovedSeq <= localSeq) ) { return 0; } @@ -786,9 +624,11 @@ export class MergeTree { } } - // TODO: remove id when segment removed - public mapIdToSegment(id: string, segment: ISegment) { - this.idToSegment.set(id, segment); + public unlinkMarker(marker: Marker) { + const id = marker.getId(); + if (id) { + this.idToMarker.delete(id); + } } private addNode(block: IMergeBlock, node: IMergeNode) { @@ -807,7 +647,7 @@ export class MergeTree { const maxChildren = MaxNodesInBlock - 1; // Starting with the leaf segments, recursively builds the B-Tree layer by layer from the bottom up. - const buildMergeBlock = (nodes: IMergeNode[]) => { + const buildMergeBlock = (nodes: IMergeNode[]): IRootMergeBlock => { const blockCount = Math.ceil(nodes.length / maxChildren); // Compute # blocks require for this level of B-Tree const blocks: IMergeBlock[] = new Array(blockCount); // Pre-alloc array to collect nodes @@ -836,7 +676,6 @@ export class MergeTree { this.blockUpdate(block); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return blocks.length === 1 // If there is only one block at this layer... ? blocks[0] // ...then we're done. Return the root. : buildMergeBlock(blocks); // ...otherwise recursively build the next layer above blocks. @@ -870,10 +709,6 @@ export class MergeTree { } } - public getCollabWindow() { - return this.collabWindow; - } - public getLength(refSeq: number, clientId: number): number { return this.blockLength(this.root, refSeq, clientId); } @@ -902,8 +737,7 @@ export class MergeTree { const children = parent.children; for (let childIndex = 0; childIndex < parent.childCount; childIndex++) { const child = children[childIndex]; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - if ((prevParent && child === prevParent) || child === node) { + if ((!!prevParent && child === prevParent) || child === node) { break; } totalOffset += this.nodeLength(child, refSeq, clientId, localSeq) ?? 0; @@ -1033,16 +867,6 @@ export class MergeTree { } } } - - // TODO:AB#4069: This update might be avoidable by checking if the old segment - // had hierarchical refs before sliding using `segment.localRefs?.hierRefCount`. - if (currentSlideDestination) { - this.blockUpdatePathLengths( - currentSlideDestination.parent, - TreeMaintenanceSequenceNumber, - LocalClientId, - ); - } }; const trySlideSegment = ( @@ -1104,7 +928,7 @@ export class MergeTree { const backwardSegmentCache = new Map(); for (const segment of segments) { assert( - isRemovedAndAcked(segment), + isRemovedAndAckedOrMovedAndAcked(segment), 0x2f1 /* slideReferences from a segment which has not been removed and acked */, ); if (segment.localRefs === undefined || segment.localRefs.empty) { @@ -1176,6 +1000,7 @@ export class MergeTree { return; } + // eslint-disable-next-line import/no-deprecated const rebaseCollabWindow = new CollaborationWindow(); rebaseCollabWindow.loadFrom(this.collabWindow); if (refSeq < this.collabWindow.minSeq) { @@ -1204,16 +1029,37 @@ export class MergeTree { return node.cachedLength; } else { this.computeLocalPartials(refSeq); + // Local client should see all segments except those after localSeq. - return node.partialLengths!.getPartialLength(refSeq, clientId, localSeq); + const partialLen = node.partialLengths!.getPartialLength( + refSeq, + clientId, + localSeq, + ); + + PartialSequenceLengths.options.verifyExpected?.( + this, + node, + refSeq, + clientId, + localSeq, + ); + + return partialLen; } } else { // Sequence number within window if (!node.isLeaf()) { - return node.partialLengths!.getPartialLength(refSeq, clientId); + const partialLen = node.partialLengths!.getPartialLength(refSeq, clientId); + + PartialSequenceLengths.options.verifyExpected?.(this, node, refSeq, clientId); + + return partialLen; } else { const segment = node; const removalInfo = toRemovalInfo(segment); + const moveInfo = toMoveInfo(segment); + if (removalInfo !== undefined) { if (seqLTE(removalInfo.removedSeq, this.collabWindow.minSeq)) { return undefined; @@ -1226,6 +1072,18 @@ export class MergeTree { } } + if (moveInfo !== undefined) { + if (seqLTE(moveInfo.movedSeq, this.collabWindow.minSeq)) { + return undefined; + } + if ( + seqLTE(moveInfo.movedSeq, refSeq) || + moveInfo.movedClientIds.includes(clientId) + ) { + return 0; + } + } + return seqLTE(node.seq ?? 0, refSeq) || segment.clientId === clientId ? segment.cachedLength : 0; @@ -1233,23 +1091,6 @@ export class MergeTree { } } - public addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) { - this.minSeqListeners ??= new Heap([], minListenerComparer); - this.minSeqListeners.add({ minRequired, onMinGE }); - } - - private notifyMinSeqListeners() { - if (this.minSeqListeners) { - while ( - this.minSeqListeners.count() > 0 && - this.minSeqListeners.peek().minRequired <= this.collabWindow.minSeq - ) { - const minListener = this.minSeqListeners.get()!; - minListener.onMinGE(this.collabWindow.minSeq); - } - } - } - public setMinSeq(minSeq: number) { assert( minSeq <= this.collabWindow.currentSeq, @@ -1264,10 +1105,11 @@ export class MergeTree { if (minSeq > this.collabWindow.minSeq) { this.collabWindow.minSeq = minSeq; + const firstMoveSeqIdx = this.moveSeqs.findIndex((seq) => seq >= minSeq); + this.moveSeqs = firstMoveSeqIdx === -1 ? [] : this.moveSeqs.slice(firstMoveSeqIdx); if (MergeTree.options.zamboniSegments) { zamboniSegments(this); } - this.notifyMinSeqListeners(); } } @@ -1284,32 +1126,12 @@ export class MergeTree { return this.getPosition(refPos, refSeq, clientId); } if (refTypeIncludesFlag(refPos, ReferenceType.Transient) || seg.localRefs?.has(refPos)) { - const offset = isRemoved(seg) ? 0 : refPos.getOffset(); + const offset = isRemoved(seg) || isMoved(seg) ? 0 : refPos.getOffset(); return offset + this.getPosition(seg, refSeq, clientId); } return DetachedReferencePosition; } - /** - * @deprecated this functionality is no longer supported and will be removed - */ - public getStackContext(startPos: number, clientId: number, rangeLabels: string[]) { - const searchInfo: IMarkerSearchRangeInfo = { - mergeTree: this, - stacks: createMap>(), - rangeLabels, - }; - - this.search( - startPos, - UniversalSequenceNumber, - clientId, - { leaf: recordRangeLeaf, shift: rangeShift }, - searchInfo, - ); - return searchInfo.stacks; - } - // TODO: filter function /** * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`. @@ -1348,7 +1170,7 @@ export class MergeTree { if (searchInfo.tile) { let pos: number; if (searchInfo.tile.isLeaf()) { - const marker = searchInfo.tile; + const marker = searchInfo.tile as Marker; pos = this.getPosition(marker, UniversalSequenceNumber, clientId); } else { const localRef = searchInfo.tile; @@ -1399,7 +1221,7 @@ export class MergeTree { foundMarker = seg; } } else { - const block = seg; + const block = seg as IHierBlock; const marker = forwards ? block.leftmostTiles[markerLabel] : block.rightmostTiles[markerLabel]; @@ -1560,8 +1382,43 @@ export class MergeTree { const deltaSegments: IMergeTreeSegmentDelta[] = []; const overlappingRemoves: boolean[] = []; pendingSegmentGroup.segments.map((pendingSegment: ISegmentLeaf) => { + const localMovedSeq = pendingSegment.localMovedSeq; const overlappingRemove = !pendingSegment.ack(pendingSegmentGroup, opArgs); + + if ( + opArgs.op.type === MergeTreeDeltaType.OBLITERATE && + localMovedSeq !== undefined + ) { + const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq); + + if (locallyMovedSegments) { + for (const segment of locallyMovedSegments.segments) { + segment.localMovedSeq = undefined; + + if (!nodesToUpdate.includes(segment.parent!)) { + nodesToUpdate.push(segment.parent!); + } + + if (segment.movedSeq === UnassignedSequenceNumber) { + segment.movedSeq = seq; + } + } + + this.locallyMovedSegments.delete(localMovedSeq); + } + } + overwrite = overlappingRemove || overwrite; + + if (opArgs.op.type === MergeTreeDeltaType.OBLITERATE) { + if (seq !== this.moveSeqs[this.moveSeqs.length - 1]) { + this.moveSeqs.push(seq); + } + if (localMovedSeq !== undefined) { + this.localMoveSeqs.delete(localMovedSeq); + } + } + overlappingRemoves.push(overlappingRemove); if (MergeTree.options.zamboniSegments) { this.addToLRUSet(pendingSegment, seq); @@ -1576,7 +1433,10 @@ export class MergeTree { // Perform slides after all segments have been acked, so that // positions after slide are final - if (opArgs.op.type === MergeTreeDeltaType.REMOVE) { + if ( + opArgs.op.type === MergeTreeDeltaType.REMOVE || + opArgs.op.type === MergeTreeDeltaType.OBLITERATE + ) { this.slideAckedRemovedSegmentReferences(pendingSegmentGroup.segments); } @@ -1599,18 +1459,18 @@ export class MergeTree { private addToPendingList( segment: ISegment, + // eslint-disable-next-line import/no-deprecated segmentGroup?: SegmentGroup, localSeq?: number, previousProps?: PropertySet, ) { let _segmentGroup = segmentGroup; if (_segmentGroup === undefined) { - // TODO: review the cast _segmentGroup = { segments: [], localSeq, refSeq: this.collabWindow.currentSeq, - } as SegmentGroup; + }; if (previousProps) { _segmentGroup.previousProps = []; } @@ -1618,9 +1478,8 @@ export class MergeTree { } if ( - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ?? is not logically equivalent when the first clause returns false. - (!_segmentGroup.previousProps && previousProps) || - (_segmentGroup.previousProps && !previousProps) + (!_segmentGroup.previousProps && !!previousProps) || + (!!_segmentGroup.previousProps && !previousProps) ) { throw new Error("All segments in group should have previousProps or none"); } @@ -1633,8 +1492,8 @@ export class MergeTree { } // TODO: error checking - public getMarkerFromId(id: string): ISegment | undefined { - return this.idToSegment.get(id); + public getMarkerFromId(id: string): Marker | undefined { + return this.idToMarker.get(id); } /** @@ -1652,7 +1511,7 @@ export class MergeTree { let pos = -1; let marker: Marker | undefined; if (relativePos.id) { - marker = this.getMarkerFromId(relativePos.id) as Marker; + marker = this.getMarkerFromId(relativePos.id); } if (marker) { pos = this.getPosition(marker, refseq, clientId); @@ -1733,7 +1592,7 @@ export class MergeTree { const { currentSeq, clientId } = this.collabWindow; - if (segmentInfo && segmentInfo.segment) { + if (segmentInfo?.segment) { const segmentPosition = this.getPosition(segmentInfo.segment, currentSeq, clientId); return segmentPosition + segmentInfo.offset!; } else { @@ -1760,9 +1619,10 @@ export class MergeTree { return siblingExists; }; + // eslint-disable-next-line import/no-deprecated let segmentGroup: SegmentGroup; const saveIfLocal = (locSegment: ISegment) => { - // Save segment so can assign sequence number when acked by server + // Save segment so we can assign sequence number when acked by server if (this.collabWindow.collaborating) { if ( locSegment.seq === UnassignedSequenceNumber && @@ -1803,7 +1663,7 @@ export class MergeTree { if (Marker.is(newSegment)) { const markerId = newSegment.getId(); if (markerId) { - this.mapIdToSegment(markerId, newSegment); + this.idToMarker.set(markerId, newSegment); } } @@ -1828,6 +1688,129 @@ export class MergeTree { saveIfLocal(newSegment); insertPos += newSegment.cachedLength; + + if (!this.options?.mergeTreeEnableObliterate) { + continue; + } + + let moveUpperBound = Number.POSITIVE_INFINITY; + const smallestSeqMoveOp = this.getSmallestSeqMoveOp(); + + if (smallestSeqMoveOp === undefined) { + continue; + } + + const leftAckedSegments: Record = {}; + const leftLocalSegments: Record = {}; + + let _localMovedSeq: number | undefined; + let _movedSeq: number | undefined; + let movedClientIds: number[] | undefined; + + const findLeftMovedSegment = (seg: ISegment) => { + const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? []; + const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : []; + for (const movedSeq of movedSeqs) { + leftAckedSegments[movedSeq] = seg; + } + + for (const localMovedSeq of localMovedSeqs) { + leftLocalSegments[localMovedSeq] = seg; + } + + if ((seg.movedSeqs?.length ?? 0) > 0 || localMovedSeqs.length > 0) { + return true; + } + + if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) { + moveUpperBound = Math.min( + moveUpperBound, + seg.seq ?? Number.POSITIVE_INFINITY, + ); + } + // If we've reached a segment that existed before any of our in-collab-window move ops + // happened, no need to continue. + return moveUpperBound >= smallestSeqMoveOp; + }; + + const findRightMovedSegment = (seg: ISegment) => { + const movedSeqs = seg.movedSeqs?.filter((movedSeq) => movedSeq >= refSeq) ?? []; + const localMovedSeqs = seg.localMovedSeq ? [seg.localMovedSeq] : []; + + for (const movedSeq of movedSeqs) { + const left = leftAckedSegments[movedSeq]; + if (left) { + _movedSeq = movedSeq; + const clientIdIdx = left.movedSeqs?.indexOf(movedSeq) ?? -1; + const movedClientId = left.movedClientIds?.[clientIdIdx]; + assert(movedClientId !== undefined, "expected client id to exist"); + movedClientIds = [movedClientId]; + return false; + } + } + + for (const localMovedSeq of localMovedSeqs) { + const left = leftLocalSegments[localMovedSeq]; + if (left) { + _localMovedSeq = localMovedSeq; + const clientIdIdx = + left.movedSeqs?.indexOf(UnassignedSequenceNumber) ?? -1; + const movedClientId = left.movedClientIds?.[clientIdIdx]; + assert(movedClientId !== undefined, "expected client id to exist"); + movedClientIds = [movedClientId]; + return false; + } + } + + if ((seg.movedSeqs?.length ?? 0) || localMovedSeqs.length > 0) { + return true; + } + + if (!isRemoved(seg) || wasRemovedAfter(seg, moveUpperBound)) { + moveUpperBound = Math.min( + moveUpperBound, + seg.seq ?? Number.POSITIVE_INFINITY, + ); + } + // If we've reached a segment that existed before any of our in-collab-window move ops + // happened, no need to continue. + return moveUpperBound >= smallestSeqMoveOp; + }; + + backwardExcursion(newSegment, findLeftMovedSegment); + moveUpperBound = Number.POSITIVE_INFINITY; + forwardExcursion(newSegment, findRightMovedSegment); + + if (_localMovedSeq !== undefined || _movedSeq !== undefined) { + assert( + movedClientIds !== undefined, + "movedClientIds should be set if local/moved seq is set", + ); + const moveInfo = { + movedClientIds, + movedSeq: _movedSeq ?? UnassignedSequenceNumber, + movedSeqs: + _movedSeq === undefined ? [UnassignedSequenceNumber] : [_movedSeq], + localMovedSeq: _localMovedSeq, + wasMovedOnInsert: (_movedSeq ?? -1) !== UnassignedSequenceNumber, + }; + + markSegmentMoved(newSegment, moveInfo); + + if (moveInfo.localMovedSeq !== undefined) { + const movedSegmentGroup = this.locallyMovedSegments.get( + moveInfo.localMovedSeq, + ); + + assert(movedSegmentGroup !== undefined, "expected segment group to exist"); + + this.addToPendingList(newSegment, movedSegmentGroup, localSeq); + } + + if (newSegment.parent) { + this.blockUpdatePathLengths(newSegment.parent, seq, clientId); + } + } } } } @@ -1867,25 +1850,37 @@ export class MergeTree { // Assume called only when pos == len private breakTie(pos: number, node: IMergeNode, seq: number) { if (node.isLeaf()) { - if (pos === 0) { - // normalize the seq numbers - // if the new seg is local (UnassignedSequenceNumber) give it the highest possible - // seq for comparison, as it will get a seq higher than any other seq once sequences - // if the current seg is local (UnassignedSequenceNumber) give it the second highest - // possible seq, as the highest is reserved for the previous. - const newSeq = seq === UnassignedSequenceNumber ? Number.MAX_SAFE_INTEGER : seq; - const segSeq = - node.seq === UnassignedSequenceNumber - ? Number.MAX_SAFE_INTEGER - 1 - : node.seq ?? 0; - return newSeq > segSeq; + if (pos !== 0) { + return false; } - return false; + + // normalize the seq numbers + // if the new seg is local (UnassignedSequenceNumber) give it the highest possible + // seq for comparison, as it will get a seq higher than any other seq once sequences + // if the current seg is local (UnassignedSequenceNumber) give it the second highest + // possible seq, as the highest is reserved for the previous. + const newSeq = seq === UnassignedSequenceNumber ? Number.MAX_SAFE_INTEGER : seq; + const segSeq = + node.seq === UnassignedSequenceNumber ? Number.MAX_SAFE_INTEGER - 1 : node.seq ?? 0; + + return ( + newSeq > segSeq || + (node.movedSeq !== undefined && + node.movedSeq !== UnassignedSequenceNumber && + node.movedSeq > seq) || + (node.removedSeq !== undefined && + node.removedSeq !== UnassignedSequenceNumber && + node.removedSeq > seq) + ); } else { return true; } } + private getSmallestSeqMoveOp(): number | undefined { + return this.moveSeqs[0] ?? (this.localMoveSeqs.size > 0 ? -1 : undefined); + } + private insertingWalk( block: IMergeBlock, pos: number, @@ -1908,14 +1903,15 @@ export class MergeTree { isLastChildBlock && !child.isLeaf() && childIndex === block.childCount - 1; const len = this.nodeLength(child, refSeq, clientId) ?? (isLastChildBlock ? 0 : undefined); + if (len === undefined) { - // if the seg len in undefined, the segment + // if the seg len is undefined, the segment // will be removed, so should just be skipped for now continue; - } else { - assert(len >= 0, 0x4bc /* Length should not be negative */); } + assert(len >= 0, 0x4bc /* Length should not be negative */); + if (_pos < len || (_pos === len && this.breakTie(_pos, child, seq))) { // Found entry containing pos if (!child.isLeaf()) { @@ -1931,11 +1927,7 @@ export class MergeTree { isLastNonLeafBlock, ); if (splitNode === undefined) { - if (context.structureChange) { - this.nodeUpdateLengthNewStructure(block); - } else { - this.blockUpdateLength(block, seq, clientId); - } + this.blockUpdateLength(block, seq, clientId); return undefined; } else if (splitNode === MergeTree.theUnfinishedNode) { _pos -= len; // Act as if shifted segment @@ -1957,9 +1949,6 @@ export class MergeTree { childIndex++; // Insert after } else { // No change - if (context.structureChange) { - this.nodeUpdateLengthNewStructure(block); - } return undefined; } } @@ -1991,15 +1980,21 @@ export class MergeTree { if (fromSplit) { this.nodeUpdateOrdinals(fromSplit); } - if (context.structureChange) { - this.nodeUpdateLengthNewStructure(block); - } else { - this.blockUpdateLength(block, seq, clientId); - } + this.blockUpdateLength(block, seq, clientId); return undefined; } else { // Don't update ordinals because higher block will do it - return this.split(block); + const newNodeFromSplit = this.split(block); + + PartialSequenceLengths.options.verifyExpected?.(this, block, refSeq, clientId); + PartialSequenceLengths.options.verifyExpected?.( + this, + newNodeFromSplit, + refSeq, + clientId, + ); + + return newNodeFromSplit; } } else { return undefined; @@ -2036,7 +2031,6 @@ export class MergeTree { * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * @param refSeq - The reference sequence number to use to apply the annotate * @param clientId - The id of the client making the annotate * @param seq - The sequence number of the annotate operation @@ -2047,7 +2041,6 @@ export class MergeTree { start: number, end: number, props: PropertySet, - combiningOp: ICombiningOp | undefined, refSeq: number, clientId: number, seq: number, @@ -2059,6 +2052,7 @@ export class MergeTree { const deltaSegments: IMergeTreeSegmentDelta[] = []; const localSeq = seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined; + // eslint-disable-next-line import/no-deprecated let segmentGroup: SegmentGroup | undefined; const annotateSegment = (segment: ISegment) => { assert( @@ -2069,9 +2063,8 @@ export class MergeTree { ); const propertyDeltas = segment.addProperties( props, - combiningOp, seq, - this.collabWindow, + this.collabWindow.collaborating, rollback, ); deltaSegments.push({ segment, propertyDeltas }); @@ -2081,7 +2074,7 @@ export class MergeTree { segment, segmentGroup, localSeq, - propertyDeltas ? propertyDeltas : {}, + propertyDeltas, ); } else { if (MergeTree.options.zamboniSegments) { @@ -2108,6 +2101,141 @@ export class MergeTree { } } + /** + * @alpha + */ + public obliterateRange( + start: number, + end: number, + refSeq: number, + clientId: number, + seq: number, + overwrite: boolean = false, + opArgs: IMergeTreeDeltaOpArgs, + ): void { + if (!this.options?.mergeTreeEnableObliterate) { + throw new UsageError("Attempted to send obliterate op without enabling feature flag."); + } + + this.ensureIntervalBoundary(start, refSeq, clientId); + this.ensureIntervalBoundary(end, refSeq, clientId); + + let _overwrite = overwrite; + const localOverlapWithRefs: ISegment[] = []; + const movedSegments: IMergeTreeSegmentDelta[] = []; + const localSeq = + seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined; + if (seq !== UnassignedSequenceNumber && seq !== this.moveSeqs[this.moveSeqs.length - 1]) { + this.moveSeqs.push(seq); + } else if (seq === UnassignedSequenceNumber && localSeq !== undefined) { + this.localMoveSeqs.add(localSeq); + } + // eslint-disable-next-line import/no-deprecated + let segmentGroup: SegmentGroup; + const markMoved = (segment: ISegment, pos: number, _start: number, _end: number) => { + const existingMoveInfo = toMoveInfo(segment); + + if ( + clientId !== segment.clientId && + segment.seq !== undefined && + seq !== UnassignedSequenceNumber && + (refSeq < segment.seq || segment.seq === UnassignedSequenceNumber) + ) { + segment.wasMovedOnInsert = true; + } + + if (existingMoveInfo !== undefined) { + _overwrite = true; + if (existingMoveInfo.movedSeq === UnassignedSequenceNumber) { + // we moved this locally, but someone else moved it first + // so put them at the head of the list + // The list isn't ordered, but we keep the first move at the head + // for partialLengths bookkeeping purposes + existingMoveInfo.movedClientIds.unshift(clientId); + + existingMoveInfo.movedSeq = seq; + existingMoveInfo.movedSeqs.unshift(seq); + if (segment.localRefs?.empty === false) { + localOverlapWithRefs.push(segment); + } + } else { + // Do not replace earlier sequence number for move + existingMoveInfo.movedClientIds.push(clientId); + existingMoveInfo.movedSeqs.push(seq); + } + } else { + segment.movedClientIds = [clientId]; + segment.movedSeq = seq; + segment.localMovedSeq = localSeq; + segment.movedSeqs = [seq]; + + movedSegments.push({ segment }); + } + + // Save segment so can assign moved sequence number when acked by server + if (this.collabWindow.collaborating) { + if ( + segment.movedSeq === UnassignedSequenceNumber && + clientId === this.collabWindow.clientId + ) { + segmentGroup = this.addToPendingList(segment, segmentGroup, localSeq); + } else { + if (MergeTree.options.zamboniSegments) { + this.addToLRUSet(segment, seq); + } + } + } + return true; + }; + + const afterMarkMoved = (node: IMergeBlock, pos: number, _start: number, _end: number) => { + if (_overwrite) { + this.nodeUpdateLengthNewStructure(node); + } else { + this.blockUpdateLength(node, seq, clientId); + } + return true; + }; + + this.nodeMap( + refSeq, + clientId, + markMoved, + undefined, + afterMarkMoved, + start, + end, + undefined, + seq !== UnassignedSequenceNumber ? seq : undefined, + ); + + this.slideAckedRemovedSegmentReferences(localOverlapWithRefs); + // opArgs == undefined => test code + if (movedSegments.length > 0) { + this.mergeTreeDeltaCallback?.(opArgs, { + operation: MergeTreeDeltaType.OBLITERATE, + deltaSegments: movedSegments, + }); + } + + if (segmentGroup! && localSeq !== undefined) { + this.locallyMovedSegments.set(localSeq, segmentGroup); + } + + // these events are newly removed + // so we slide after eventing in case the consumer wants to make reference + // changes at remove time, like add a ref to track undo redo. + if (!this.collabWindow.collaborating || clientId !== this.collabWindow.clientId) { + this.slideAckedRemovedSegmentReferences(movedSegments.map(({ segment }) => segment)); + } + + if (this.collabWindow.collaborating && seq !== UnassignedSequenceNumber) { + if (MergeTree.options.zamboniSegments) { + zamboniSegments(this); + } + } + } + public markRangeRemoved( start: number, end: number, @@ -2120,6 +2248,7 @@ export class MergeTree { let _overwrite = overwrite; this.ensureIntervalBoundary(start, refSeq, clientId); this.ensureIntervalBoundary(end, refSeq, clientId); + // eslint-disable-next-line import/no-deprecated let segmentGroup: SegmentGroup; const removedSegments: IMergeTreeSegmentDelta[] = []; const localOverlapWithRefs: ISegment[] = []; @@ -2127,6 +2256,7 @@ export class MergeTree { seq === UnassignedSequenceNumber ? ++this.collabWindow.localSeq : undefined; const markRemoved = (segment: ISegment, pos: number, _start: number, _end: number) => { const existingRemovalInfo = toRemovalInfo(segment); + if (existingRemovalInfo !== undefined) { _overwrite = true; if (existingRemovalInfo.removedSeq === UnassignedSequenceNumber) { @@ -2152,7 +2282,7 @@ export class MergeTree { removedSegments.push({ segment }); } - // Save segment so can assign removed sequence number when acked by server + // Save segment so we can assign removed sequence number when acked by server if (this.collabWindow.collaborating) { if ( segment.removedSeq === UnassignedSequenceNumber && @@ -2203,6 +2333,7 @@ export class MergeTree { /** * Revert an unacked local op */ + // eslint-disable-next-line import/no-deprecated public rollback(op: IMergeTreeDeltaOp, localOpMetadata: SegmentGroup) { if (op.type === MergeTreeDeltaType.REMOVE) { const pendingSegmentGroup = this.pendingSegments.pop?.()?.data; @@ -2283,26 +2414,20 @@ export class MergeTree { ); } /* op.type === MergeTreeDeltaType.ANNOTATE */ else { const props = pendingSegmentGroup.previousProps![i]; - const rollbackType = - op.combiningOp?.name === "rewrite" - ? PropertiesRollback.Rewrite - : PropertiesRollback.Rollback; const annotateOp = createAnnotateRangeOp( start, start + segment.cachedLength, props, - undefined, ); this.annotateRange( start, start + segment.cachedLength, props, - undefined, UniversalSequenceNumber, this.collabWindow.clientId, UniversalSequenceNumber, { op: annotateOp }, - rollbackType, + PropertiesRollback.Rollback, ); i++; } @@ -2346,17 +2471,7 @@ export class MergeTree { lref: LocalReferencePosition, ): LocalReferencePosition | undefined { const segment: ISegmentLeaf | undefined = lref.getSegment(); - if (segment) { - const removedRefs = segment?.localRefs?.removeLocalRef(lref); - if (removedRefs !== undefined && refTypeIncludesFlag(lref, hierRefTypes)) { - this.blockUpdatePathLengths( - segment.parent, - TreeMaintenanceSequenceNumber, - LocalClientId, - ); - } - return removedRefs; - } + return segment?.localRefs?.removeLocalRef(lref); } startOfTree = new StartOfTreeSegment(this); @@ -2373,12 +2488,12 @@ export class MergeTree { if ( _segment !== "start" && _segment !== "end" && - isRemovedAndAcked(_segment) && + isRemovedAndAckedOrMovedAndAcked(_segment) && !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) && _segment.endpointType === undefined ) { throw new UsageError( - "Can only create SlideOnRemove or Transient local reference position on a removed segment", + "Can only create SlideOnRemove or Transient local reference position on a removed or obliterated segment", ); } @@ -2403,18 +2518,11 @@ export class MergeTree { canSlideToEndpoint, ); - if (refTypeIncludesFlag(refType, hierRefTypes)) { - this.blockUpdatePathLengths( - segment.parent, - TreeMaintenanceSequenceNumber, - LocalClientId, - ); - } return segRef; } // Segments should either be removed remotely, removed locally, or inserted locally - private normalizeAdjacentSegments(affectedSegments: List): void { + private normalizeAdjacentSegments(affectedSegments: DoublyLinkedList): void { // Eagerly demand this since we're about to shift elements in the list around const currentOrder = Array.from(affectedSegments, ({ data: seg }) => ({ parent: seg.parent, @@ -2507,7 +2615,7 @@ export class MergeTree { newOrder.forEach(computeDepth); for (const [node] of Array.from(depths.entries()).sort((a, b) => b[1] - a[1])) { if (!node.isLeaf()) { - this.nodeUpdateLengthNewStructure(node, false); + this.nodeUpdateLengthNewStructure(node); } } newOrder.forEach( @@ -2536,7 +2644,7 @@ export class MergeTree { * it can fix up its local state to align with what would be expected of the op it resubmits. */ public normalizeSegmentsOnRebase(): void { - let currentRangeToNormalize = new List(); + let currentRangeToNormalize = new DoublyLinkedList(); let rangeContainsLocalSegs = false; let rangeContainsRemoteRemovedSegs = false; const normalize = () => { @@ -2559,7 +2667,7 @@ export class MergeTree { currentRangeToNormalize.push(seg); } else { normalize(); - currentRangeToNormalize = new List(); + currentRangeToNormalize = new DoublyLinkedList(); rangeContainsLocalSegs = false; rangeContainsRemoteRemovedSegs = false; } @@ -2570,13 +2678,42 @@ export class MergeTree { normalize(); } + private addNodeReferences( + node: IMergeNode, + rightmostTiles: MapLike, + leftmostTiles: MapLike, + ) { + if (node.isLeaf()) { + const segment = node; + if ((this.localNetLength(segment) ?? 0) > 0 && Marker.is(segment)) { + const markerId = segment.getId(); + // Also in insertMarker but need for reload segs case + // can add option for this only from reload segs + if (markerId) { + this.idToMarker.set(markerId, segment); + } + if (refTypeIncludesFlag(segment, ReferenceType.Tile)) { + addTile(segment, rightmostTiles); + addTileIfNotPresent(segment, leftmostTiles); + } + } + } else { + const block = node as IHierBlock; + // eslint-disable-next-line import/no-deprecated + extend(rightmostTiles, block.rightmostTiles); + // eslint-disable-next-line import/no-deprecated + extendIfUndefined(leftmostTiles, block.leftmostTiles); + } + } + private blockUpdate(block: IMergeBlock) { let len: number | undefined; const hierBlock = block.hierBlock(); if (hierBlock) { + // eslint-disable-next-line import/no-deprecated hierBlock.rightmostTiles = createMap(); + // eslint-disable-next-line import/no-deprecated hierBlock.leftmostTiles = createMap(); - hierBlock.rangeStacks = {}; } for (let i = 0; i < block.childCount; i++) { const child = block.children[i]; @@ -2586,13 +2723,7 @@ export class MergeTree { len += nodeLength; } if (hierBlock) { - addNodeReferences( - this, - child, - hierBlock.rightmostTiles, - hierBlock.leftmostTiles, - hierBlock.rangeStacks, - ); + this.addNodeReferences(child, hierBlock.rightmostTiles, hierBlock.leftmostTiles); } } @@ -2633,9 +2764,18 @@ export class MergeTree { } else { node.partialLengths = PartialSequenceLengths.combine(node, this.collabWindow); } + + PartialSequenceLengths.options.verifyExpected?.(this, node, seq, clientId); } } + /** + * Map over all visible segments in a given range + * + * A segment is visible if its length is greater than 0 + * + * See `this.nodeMap` for additional documentation + */ public mapRange( handler: ISegmentAction, refSeq: number, @@ -2644,6 +2784,7 @@ export class MergeTree { start?: number, end?: number, splitRange: boolean = false, + visibilitySeq: number = refSeq, ) { if (splitRange) { if (start) { @@ -2653,57 +2794,42 @@ export class MergeTree { this.ensureIntervalBoundary(end, refSeq, clientId); } } - this.nodeMap(refSeq, clientId, handler, accum, undefined, start, end); - } - - public incrementalBlockMap(stateStack: Stack>) { - while (!stateStack.empty()) { - // We already check the stack is not empty - const state = stateStack.top()!; - if (state.op !== IncrementalExecOp.Go) { - return; - } - if (state.childIndex === 0) { - state.start ??= 0; - state.end ??= this.blockLength(state.block, state.refSeq, state.clientId); - state.actions.pre?.(state); - } - if (state.op === IncrementalExecOp.Go && state.childIndex < state.block.childCount) { - const child = state.block.children[state.childIndex]; - const len = this.nodeLength(child, state.refSeq, state.clientId) ?? 0; - if (len > 0 && state.start < len && state.end > 0) { - if (!child.isLeaf()) { - const childState = new IncrementalMapState( - child, - state.actions, - state.pos, - state.refSeq, - state.clientId, - state.context, - state.start, - state.end, - 0, - ); - stateStack.push(childState); - } else { - state.actions.leaf(child, state); - } - } - state.pos += len; - state.start -= len; - state.end -= len; - state.childIndex++; - } else { - if (state.childIndex === state.block.childCount) { - if (state.op === IncrementalExecOp.Go) { - state.actions.post?.(state); - } - stateStack.pop(); - } - } - } + this.nodeMap( + refSeq, + clientId, + handler, + accum, + undefined, + start, + end, + undefined, + visibilitySeq, + ); } + /** + * Map over all visible segments in a given range + * + * A segment is visible if its length is greater than 0 + * + * @param refSeq - The sequence number used to determine the range (start + * and end positions) of segments to iterate over. + * + * @param visibilitySeq - An additional sequence number to further configure + * segment visibility during traversal. This is the same as refSeq, except + * in the case of obliterate. + * + * In the case where `refSeq == visibilitySeq`, mapping is done on all + * visible segments from `start` to `end`. + * + * If a segment is invisible at both `visibilitySeq` and `refSeq`, then it + * will not be traversed and mapped. Otherwise, if the segment is visible at + * either seq, it will be mapped. + * + * If a segment is only visible at `visibilitySeq`, it will still be mapped, + * but it will not count as a segment within the range. That is, it will be + * ignored for the purposes of tracking when traversal should end. + */ private nodeMap( refSeq: number, clientId: number, @@ -2713,6 +2839,7 @@ export class MergeTree { start: number = 0, end?: number, localSeq?: number, + visibilitySeq: number = refSeq, ): void { const endPos = end ?? this.nodeLength(this.root, refSeq, clientId, localSeq) ?? 0; if (endPos === start) { @@ -2728,12 +2855,25 @@ export class MergeTree { if (endPos <= pos) { return NodeAction.Exit; } - const len = this.nodeLength(node, refSeq, clientId, localSeq); - if (len === undefined || len === 0) { + + const len = this.nodeLength(node, visibilitySeq, clientId, localSeq); + const lenAtRefSeq = + (visibilitySeq === refSeq + ? len + : this.nodeLength(node, refSeq, clientId, localSeq)) ?? 0; + + const isUnackedAndInObliterate = + visibilitySeq !== refSeq && + (!node.isLeaf() || node.seq === UnassignedSequenceNumber); + + if ( + (len === undefined && lenAtRefSeq === 0) || + (len === 0 && !isUnackedAndInObliterate && lenAtRefSeq === 0) + ) { return NodeAction.Skip; } - const nextPos = pos + len; + const nextPos = pos + lenAtRefSeq; // start is beyond the current node, so we can skip it if (start >= nextPos) { pos = nextPos; diff --git a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts index 8f8106b1f5b9..ede545210e59 100644 --- a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts +++ b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts @@ -14,7 +14,8 @@ import { ISegment } from "./mergeTreeNodes"; export type MergeTreeDeltaOperationType = | typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT - | typeof MergeTreeDeltaType.REMOVE; + | typeof MergeTreeDeltaType.REMOVE + | typeof MergeTreeDeltaType.OBLITERATE; /** * Enum-like constant defining the types of "maintenance" events on a merge tree. @@ -115,7 +116,6 @@ export interface IMergeTreeClientSequenceArgs { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export type MergeTreeDeltaCallback = ( @@ -131,7 +131,6 @@ export interface IMergeTreeMaintenanceCallbackArgs extends IMergeTreeDeltaCallbackArgs {} /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export type MergeTreeMaintenanceCallback = ( diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index ad853a17daeb..aee688b4fc6b 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -4,26 +4,21 @@ */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable import/no-deprecated */ import { assert } from "@fluidframework/core-utils"; import { AttributionKey } from "@fluidframework/runtime-definitions"; import { IAttributionCollection } from "./attributionCollection"; import { LocalClientId, UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; import { LocalReferenceCollection } from "./localReference"; +import { ISegmentLeaf } from "./mergeTree"; import { IMergeTreeDeltaOpArgs } from "./mergeTreeDeltaCallback"; import { TrackingGroupCollection } from "./mergeTreeTracking"; -import { ICombiningOp, IJSONSegment, IMarkerDef, MergeTreeDeltaType, ReferenceType } from "./ops"; +import { IJSONSegment, IMarkerDef, MergeTreeDeltaType, ReferenceType } from "./ops"; import { computeHierarchicalOrdinal } from "./ordinal"; import { PartialSequenceLengths } from "./partialLengths"; +// eslint-disable-next-line import/no-deprecated import { clone, createMap, MapLike, PropertySet } from "./properties"; -import { - refTypeIncludesFlag, - RangeStackMap, - ReferencePosition, - refGetRangeLabels, - refGetTileLabels, -} from "./referencePositions"; +import { refTypeIncludesFlag, ReferencePosition, refGetTileLabels } from "./referencePositions"; import { SegmentGroupCollection } from "./segmentGroupCollection"; import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager"; @@ -87,10 +82,8 @@ export interface IMergeBlock extends IMergeNodeCommon { * @internal */ export interface IHierBlock extends IMergeBlock { - hierToString(indentCount: number): string; rightmostTiles: MapLike; leftmostTiles: MapLike; - rangeStacks: RangeStackMap; } /** @@ -116,7 +109,6 @@ export interface IRemovalInfo { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function toRemovalInfo(maybe: Partial | undefined): IRemovalInfo | undefined { @@ -129,12 +121,91 @@ export function toRemovalInfo(maybe: Partial | undefined): IRemova ); } +/** + * Tracks information about when and where this segment was moved to. + * + * Note that merge-tree does not currently support moving and only supports + * obliterate. The fields below include "move" in their names to avoid renaming + * in the future, when moves _are_ supported. + * @alpha + */ +export interface IMoveInfo { + /** + * Local seq at which this segment was moved if the move is yet-to-be + * acked. + */ + localMovedSeq?: number; + + /** + * The first seq at which this segment was moved. + */ + movedSeq: number; + + /** + * All seqs at which this segment was moved. In the case of overlapping, + * concurrent moves this array will contain multiple seqs. + * + * The seq at `movedSeqs[i]` corresponds to the client id at `movedClientIds[i]`. + * + * The first element corresponds to the seq of the first move + */ + movedSeqs: number[]; + + /** + * A reference to the inserted destination segment corresponding to this + * segment's move. + * + * If undefined, the move was an obliterate. + * + * Currently this field is unused, as we only support obliterate operations + */ + moveDst?: ReferencePosition; + + /** + * List of client IDs that have moved this segment. + * + * The client that actually moved the segment (i.e. whose move op was sequenced + * first) is stored as the first client in this list. Other clients in the + * list have all issued concurrent ops to move the segment. + */ + movedClientIds: number[]; + + /** + * If this segment was inserted into a concurrently moved range and + * the move op was sequenced before the insertion op. In this case, + * the segment is visible only to the inserting client + * + * `wasMovedOnInsert` only applies for acked obliterates. That is, if + * a segment inserted by a remote client is moved on insertion by a local + * and unacked obliterate, we do not consider it as having been moved + * on insert + * + * If a segment is moved on insertion, its length is only ever visible to + * the client that inserted the segment. This is relevant in partial length + * calculations + */ + wasMovedOnInsert: boolean; +} + +export function toMoveInfo(maybe: Partial | undefined): IMoveInfo | undefined { + if (maybe?.movedClientIds !== undefined && maybe?.movedSeq !== undefined) { + return maybe as IMoveInfo; + } + assert( + maybe?.movedClientIds === undefined && + maybe?.movedSeq === undefined && + maybe?.movedSeqs === undefined && + maybe?.wasMovedOnInsert === undefined, + "movedClientIds, movedSeq, wasMovedOnInsert, and movedSeqs should all be either set or not set", + ); +} + /** * A segment representing a portion of the merge tree. * Segments are leaf nodes of the merge tree and contain data. * @alpha */ -export interface ISegment extends IMergeNodeCommon, Partial { +export interface ISegment extends IMergeNodeCommon, Partial, Partial { readonly type: string; readonly segmentGroups: SegmentGroupCollection; readonly trackingCollection: TrackingGroupCollection; @@ -208,13 +279,19 @@ export interface ISegment extends IMergeNodeCommon, Partial { * Properties that have been added to this segment via annotation. */ properties?: PropertySet; + + /** + * Add properties to this segment via annotation. + * + * @remarks This function should not be called directly. Properties should + * be added through the `annotateRange` functions. + */ addProperties( newProps: PropertySet, - op?: ICombiningOp, seq?: number, - collabWindow?: CollaborationWindow, + collaborating?: boolean, rollback?: PropertiesRollback, - ): PropertySet | undefined; + ): PropertySet; clone(): ISegment; canAppend(segment: ISegment): boolean; append(segment: ISegment): void; @@ -231,14 +308,11 @@ export interface ISegment extends IMergeNodeCommon, Partial { * @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. - * - * @deprecated This functionality was not meant to be exported and will be removed in a future release */ ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export interface IMarkerModifiedAction { @@ -299,33 +373,12 @@ export interface NodeAction { clientData: TClientData, ): boolean; } -/** - * @internal - */ -export interface IncrementalSegmentAction { - (segment: ISegment, state: IncrementalMapState); -} - -/** - * @internal - */ -export interface IncrementalBlockAction { - (state: IncrementalMapState); -} -/** - * @internal - * */ -export interface BlockUpdateActions { - child: (block: IMergeBlock, index: number) => void; -} /** * @internal */ export interface InsertContext { candidateSegment?: ISegment; - prepareEvents?: boolean; - structureChange?: boolean; leaf: (segment: ISegment | undefined, pos: number, ic: InsertContext) => ISegmentChanges; continuePredicate?: (continueFromBlock: IMergeBlock) => boolean; } @@ -340,31 +393,15 @@ export interface SegmentActions { pre?: BlockAction; post?: BlockAction; } -/** - * @internal - */ -export interface IncrementalSegmentActions { - leaf: IncrementalSegmentAction; - pre?: IncrementalBlockAction; - post?: IncrementalBlockAction; -} - -/** - * @internal - */ -export interface SearchResult { - text: string; - pos: number; -} /** * @deprecated This functionality was not meant to be exported and will be removed in a future release * @alpha */ export interface SegmentGroup { - segments: ISegment[]; + segments: ISegmentLeaf[]; previousProps?: PropertySet[]; - localSeq: number; + localSeq?: number; refSeq: number; } @@ -440,6 +477,10 @@ export abstract class BaseSegment extends MergeNode implements ISegment { public seq: number = UniversalSequenceNumber; public removedSeq?: number; public removedClientIds?: number[]; + public movedSeq?: number; + public movedSeqs?: number[]; + public movedClientIds?: number[]; + public wasMovedOnInsert?: boolean | undefined; public readonly segmentGroups: SegmentGroupCollection = new SegmentGroupCollection(this); public readonly trackingCollection: TrackingGroupCollection = new TrackingGroupCollection(this); /***/ @@ -450,22 +491,22 @@ export abstract class BaseSegment extends MergeNode implements ISegment { public abstract readonly type: string; public localSeq?: number; public localRemovedSeq?: number; + public localMovedSeq?: number; public addProperties( newProps: PropertySet, - op?: ICombiningOp, seq?: number, - collabWindow?: CollaborationWindow, + collaborating?: boolean, rollback: PropertiesRollback = PropertiesRollback.None, ) { this.propertyManager ??= new PropertiesManager(); + // eslint-disable-next-line import/no-deprecated this.properties ??= createMap(); return this.propertyManager.addProperties( this.properties, newProps, - op, seq, - collabWindow && collabWindow.collaborating, + collaborating, rollback, ); } @@ -474,17 +515,22 @@ export abstract class BaseSegment extends MergeNode implements ISegment { return !!this.properties && this.properties[key] !== undefined; } - public isLeaf() { + public isLeaf(): this is ISegment { return true; } protected cloneInto(b: ISegment) { b.clientId = this.clientId; // TODO: deep clone properties + // eslint-disable-next-line import/no-deprecated b.properties = clone(this.properties); b.removedClientIds = this.removedClientIds?.slice(); // TODO: copy removed client overlap and branch removal info b.removedSeq = this.removedSeq; + b.movedClientIds = this.movedClientIds?.slice(); + b.movedSeq = this.movedSeq; + b.movedSeqs = this.movedSeqs; + b.wasMovedOnInsert = this.wasMovedOnInsert; b.seq = this.seq; b.attribution = this.attribution?.clone(); } @@ -501,9 +547,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { public abstract toJSONObject(): any; - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ + /***/ public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean { const currentSegmentGroup = this.segmentGroups.dequeue(); assert( @@ -541,42 +585,68 @@ export abstract class BaseSegment extends MergeNode implements ISegment { } return false; + case MergeTreeDeltaType.OBLITERATE: + const moveInfo: IMoveInfo | undefined = toMoveInfo(this); + assert(moveInfo !== undefined, "On obliterate ack, missing move info!"); + this.localMovedSeq = undefined; + const seqIdx = moveInfo.movedSeqs.indexOf(UnassignedSequenceNumber); + assert(seqIdx !== -1, "expected movedSeqs to contain unacked seq"); + moveInfo.movedSeqs[seqIdx] = 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) { - const leafSegment: IMergeLeaf | undefined = this.createSplitSegmentAt(pos); - if (leafSegment) { - this.copyPropertiesTo(leafSegment); - // eslint-disable-next-line @typescript-eslint/no-this-alias - const thisAsMergeSegment: IMergeLeaf = this; - leafSegment.parent = thisAsMergeSegment.parent; - - // Give the leaf a temporary yet valid ordinal. - // when this segment is put in the tree, it will get its real ordinal, - // but this ordinal meets all the necessary invariants for now. - leafSegment.ordinal = this.ordinal + String.fromCharCode(0); - - leafSegment.removedClientIds = this.removedClientIds?.slice(); - leafSegment.removedSeq = this.removedSeq; - leafSegment.localRemovedSeq = this.localRemovedSeq; - leafSegment.seq = this.seq; - leafSegment.localSeq = this.localSeq; - leafSegment.clientId = this.clientId; - this.segmentGroups.copyTo(leafSegment); - this.trackingCollection.copyTo(leafSegment); - if (this.localRefs) { - this.localRefs.split(pos, leafSegment); - } - if (this.attribution) { - leafSegment.attribution = this.attribution.splitAt(pos); - } - } - return leafSegment; + if (pos <= 0) { + return undefined; + } + + const leafSegment: IMergeLeaf | undefined = this.createSplitSegmentAt(pos); + + if (!leafSegment) { + return undefined; + } + + this.copyPropertiesTo(leafSegment); + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisAsMergeSegment: IMergeLeaf = this; + leafSegment.parent = thisAsMergeSegment.parent; + + // Give the leaf a temporary yet valid ordinal. + // when this segment is put in the tree, it will get its real ordinal, + // but this ordinal meets all the necessary invariants for now. + leafSegment.ordinal = this.ordinal + String.fromCharCode(0); + + leafSegment.removedClientIds = this.removedClientIds?.slice(); + leafSegment.removedSeq = this.removedSeq; + leafSegment.localRemovedSeq = this.localRemovedSeq; + leafSegment.seq = this.seq; + leafSegment.localSeq = this.localSeq; + leafSegment.clientId = this.clientId; + leafSegment.movedClientIds = this.movedClientIds?.slice(); + leafSegment.movedSeq = this.movedSeq; + leafSegment.movedSeqs = this.movedSeqs?.slice(); + leafSegment.localMovedSeq = this.localMovedSeq; + leafSegment.wasMovedOnInsert = this.wasMovedOnInsert; + this.segmentGroups.copyTo(leafSegment); + this.trackingCollection.copyTo(leafSegment); + if (this.localRefs) { + this.localRefs.split(pos, leafSegment); + } + if (this.attribution) { + leafSegment.attribution = this.attribution.splitAt(pos); } + + return leafSegment; } private copyPropertiesTo(other: ISegment) { @@ -619,6 +689,10 @@ export abstract class BaseSegment extends MergeNode implements ISegment { } /** + * The special-cased property key that tracks the id of a {@link Marker}. + * + * @remarks In general, marker ids should be accessed using the inherent method + * {@link Marker.getId}. Marker ids should not be updated after creation. * @internal */ export const reservedMarkerIdKey = "markerId"; @@ -635,9 +709,17 @@ export interface IJSONMarkerSegment extends IJSONSegment { } /** + * Markers are a special kind of segment that do not hold any content. + * + * Markers with a reference type of {@link ReferenceType.Tile} support spatially + * accelerated queries for finding the next marker to the left or right of it in + * sub-linear time. This is useful, for example, in the case of jumping from the + * start of a paragraph to the end, assuming a paragraph is bound by markers at + * the start and end. + * * @alpha */ -export class Marker extends BaseSegment implements ReferencePosition { +export class Marker extends BaseSegment implements ReferencePosition, ISegment { public static readonly type = "Marker"; public static is(segment: ISegment): segment is Marker { return segment.type === Marker.type; @@ -684,10 +766,6 @@ export class Marker extends BaseSegment implements ReferencePosition { return 0; } - hasSimpleType(simpleTypeName: string) { - return !!this.properties && this.properties[reservedMarkerSimpleTypeKey] === simpleTypeName; - } - getProperties() { return this.properties; } @@ -712,31 +790,6 @@ export class Marker extends BaseSegment implements ReferencePosition { throw new Error("Can not append to marker"); } } -/** - * @internal - */ -export enum IncrementalExecOp { - Go, - Stop, - Yield, -} -/** - * @internal - */ -export class IncrementalMapState { - op = IncrementalExecOp.Go; - constructor( - public block: IMergeBlock, - public actions: IncrementalSegmentActions, - public pos: number, - public refSeq: number, - public clientId: number, - public context: TContext, - public start: number, - public end: number, - public childIndex = 0, - ) {} -} /** * @deprecated This functionality was not meant to be exported and will be removed in a future release @@ -829,57 +882,20 @@ export class CollaborationWindow { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const compareNumbers = (a: number, b: number) => a - b; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export const compareStrings = (a: string, b: string) => a.localeCompare(b); -const indentStrings = ["", " ", " "]; -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - * @internal - */ -export function internedSpaces(n: number) { - if (indentStrings[n] === undefined) { - indentStrings[n] = ""; - for (let i = 0; i < n; i++) { - indentStrings[n] += " "; - } - } - return indentStrings[n]; -} - -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @internal - */ -export interface IConsensusInfo { - marker: Marker; - callback: (m: Marker) => void; -} - -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @internal - */ -export interface SegmentAccumulator { - segments: ISegment[]; -} -/** - * @internal - */ -export interface MinListener { - minRequired: number; - onMinGE(minSeq: number): void; -} - /** + * Get a human-readable string for a given {@link Marker}. + * + * @remarks This function is intended for debugging only. The exact format of + * this string should not be relied upon between versions. * @internal */ export function debugMarkerToString(marker: Marker): string { @@ -887,18 +903,6 @@ export function debugMarkerToString(marker: Marker): string { if (refTypeIncludesFlag(marker, ReferenceType.Tile)) { bbuf += "Tile"; } - if (refTypeIncludesFlag(marker, ReferenceType.NestBegin)) { - if (bbuf.length > 0) { - bbuf += "; "; - } - bbuf += "RangeBegin"; - } - if (refTypeIncludesFlag(marker, ReferenceType.NestEnd)) { - if (bbuf.length > 0) { - bbuf += "; "; - } - bbuf += "RangeEnd"; - } let lbuf = ""; const id = marker.getId(); if (id) { @@ -915,25 +919,7 @@ export function debugMarkerToString(marker: Marker): string { lbuf += tileLabel; } } - const rangeLabels = refGetRangeLabels(marker); - if (rangeLabels) { - let rangeKind = "begin"; - if (refTypeIncludesFlag(marker, ReferenceType.NestEnd)) { - rangeKind = "end"; - } - if (tileLabels) { - lbuf += " "; - } - lbuf += `range ${rangeKind} -- `; - const labels = rangeLabels; - for (let i = 0, len = labels.length; i < len; i++) { - const rangeLabel = labels[i]; - if (i > 0) { - lbuf += "; "; - } - lbuf += rangeLabel; - } - } + let pbuf = ""; if (marker.properties) { pbuf += JSON.stringify(marker.properties, (key, value) => { diff --git a/packages/dds/merge-tree/src/mergeTreeTracking.ts b/packages/dds/merge-tree/src/mergeTreeTracking.ts index 025bb9328212..6ca479a8fc62 100644 --- a/packages/dds/merge-tree/src/mergeTreeTracking.ts +++ b/packages/dds/merge-tree/src/mergeTreeTracking.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { LocalReferencePosition } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; +// eslint-disable-next-line import/no-deprecated import { SortedSegmentSet } from "./sortedSegmentSet"; /** @@ -29,9 +28,11 @@ export interface ITrackingGroup { * @alpha */ export class TrackingGroup implements ITrackingGroup { + // eslint-disable-next-line import/no-deprecated private readonly trackedSet: SortedSegmentSet; constructor() { + // eslint-disable-next-line import/no-deprecated this.trackedSet = new SortedSegmentSet(); } @@ -102,6 +103,7 @@ export class UnorderedTrackingGroup implements ITrackingGroup { } /** + * A collection of {@link ITrackingGroup}. * @alpha */ export class TrackingGroupCollection { diff --git a/packages/dds/merge-tree/src/opBuilder.ts b/packages/dds/merge-tree/src/opBuilder.ts index 2d5c376d49e5..0b01a8740b00 100644 --- a/packages/dds/merge-tree/src/opBuilder.ts +++ b/packages/dds/merge-tree/src/opBuilder.ts @@ -2,17 +2,18 @@ * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ import { ISegment, Marker } from "./mergeTreeNodes"; import { - ICombiningOp, IMergeTreeAnnotateMsg, + // eslint-disable-next-line import/no-deprecated IMergeTreeGroupMsg, IMergeTreeInsertMsg, IMergeTreeRemoveMsg, MergeTreeDeltaType, IMergeTreeDeltaOp, + // eslint-disable-next-line import/no-deprecated + IMergeTreeObliterateMsg, } from "./ops"; import { PropertySet } from "./properties"; @@ -20,16 +21,13 @@ import { PropertySet } from "./properties"; * Creates the op for annotating the markers with the provided properties * @param marker - The marker to annotate * @param props - The properties to annotate the marker with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * @returns The annotate op * - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function createAnnotateMarkerOp( marker: Marker, props: PropertySet, - combiningOp?: ICombiningOp, ): IMergeTreeAnnotateMsg | undefined { const id = marker.getId(); if (!id) { @@ -37,7 +35,6 @@ export function createAnnotateMarkerOp( } return { - combiningOp, props, relativePos1: { id, before: true }, relativePos2: { id }, @@ -50,20 +47,16 @@ export function createAnnotateMarkerOp( * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * @returns The annotate op * - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function createAnnotateRangeOp( start: number, end: number, props: PropertySet, - combiningOp: ICombiningOp | undefined, ): IMergeTreeAnnotateMsg { return { - combiningOp, pos1: start, pos2: end, props, @@ -77,7 +70,6 @@ export function createAnnotateRangeOp( * @param start - The inclusive start of the range to remove * @param end - The exclusive end of the range to remove * - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemoveMsg { @@ -88,12 +80,28 @@ export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemov }; } +/** + * Creates the op to obliterate a range + * + * @param start - The inclusive start of the range to obliterate + * @param end - The exclusive end of the range to obliterate + * + * @internal + */ +// eslint-disable-next-line import/no-deprecated +export function createObliterateRangeOp(start: number, end: number): IMergeTreeObliterateMsg { + return { + pos1: start, + pos2: end, + type: MergeTreeDeltaType.OBLITERATE, + }; +} + /** * * @param pos - The position to insert the segment at * @param segment - The segment to insert * - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTreeInsertMsg { @@ -101,7 +109,6 @@ export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTre } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg { @@ -116,9 +123,14 @@ export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg { * * @param ops - The ops to group * + * @deprecated The ability to create group ops will be removed in an upcoming + * release, as group ops are redundant with he native batching capabilities of + * the runtime + * * @deprecated The ability to create group ops will be removed in an upcoming release, as group ops are redundant with he native batching capabilities of the runtime * @internal */ +// eslint-disable-next-line import/no-deprecated export function createGroupOp(...ops: IMergeTreeDeltaOp[]): IMergeTreeGroupMsg { return { ops, diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index c47dd7f3b040..41ecdac63e2e 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -4,7 +4,7 @@ */ /** - * Flags enum that dictates behavior of a ReferencePosition + * Flags enum that dictates behavior of a {@link ReferencePosition} * @alpha */ export enum ReferenceType { @@ -13,16 +13,21 @@ export enum ReferenceType { * Allows this reference to be located using the `findTile` API on merge-tree. */ Tile = 0x1, + /** - * @deprecated this functionality is no longer supported and will be removed + * Denotes that this reference begins the start of an interval. This is + * generally not meaningful outside the context of interval collections + * on SharedString. */ - NestBegin = 0x2, + RangeBegin = 0x10, + /** - * @deprecated this functionality is no longer supported and will be removed + * Denotes that this reference is the end of an interval. This is + * generally not meaningful outside the context of interval collections + * on SharedString. */ - NestEnd = 0x4, - RangeBegin = 0x10, RangeEnd = 0x20, + /** * When a segment is marked removed (locally or with ack), this reference will slide to the first * valid option of: @@ -61,6 +66,7 @@ export const MergeTreeDeltaType = { * @deprecated The ability to create group ops will be removed in an upcoming release, as group ops are redundant with he native batching capabilities of the runtime */ GROUP: 3, + OBLITERATE: 4, } as const; /** @@ -128,11 +134,20 @@ export interface IMergeTreeRemoveMsg extends IMergeTreeDelta { * functionality. * @alpha */ -export interface ICombiningOp { - name: string; - defaultValue?: any; - minValue?: any; - maxValue?: any; +export interface IMergeTreeObliterateMsg extends IMergeTreeDelta { + type: typeof MergeTreeDeltaType.OBLITERATE; + pos1?: number; + /** + * This field is currently unused, but we keep it around to make the union + * type of all merge-tree messages have the same fields + */ + relativePos1?: never; + pos2?: number; + /** + * This field is currently unused, but we keep it around to make the union + * type of all merge-tree messages have the same fields + */ + relativePos2?: never; } /** @@ -145,16 +160,13 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { pos2?: number; relativePos2?: IRelativePosition; props: Record; - /** - * @deprecated We no longer intend to support this functionality and it will - * be removed in a future release. There is no replacement for this - * functionality. - */ - combiningOp?: ICombiningOp; } /** - * @deprecated The ability to create group ops will be removed in an upcoming release, as group ops are redundant with the native batching capabilities of the runtime + * @deprecated The ability to create group ops will be removed in an upcoming + * release, as group ops are redundant with the native batching capabilities + * of the runtime + * * @alpha */ export interface IMergeTreeGroupMsg extends IMergeTreeDelta { @@ -172,7 +184,11 @@ export interface IJSONSegment { /** * @alpha */ -export type IMergeTreeDeltaOp = IMergeTreeInsertMsg | IMergeTreeRemoveMsg | IMergeTreeAnnotateMsg; +export type IMergeTreeDeltaOp = + | IMergeTreeInsertMsg + | IMergeTreeRemoveMsg + | IMergeTreeAnnotateMsg + | IMergeTreeObliterateMsg; /** * @alpha diff --git a/packages/dds/merge-tree/src/ordinal.ts b/packages/dds/merge-tree/src/ordinal.ts index de5f62cf4064..5e9f4318e9e0 100644 --- a/packages/dds/merge-tree/src/ordinal.ts +++ b/packages/dds/merge-tree/src/ordinal.ts @@ -29,9 +29,3 @@ export function computeHierarchicalOrdinal( return ordinal; } - -export function computeNumericOrdinal(index: number) { - const prefixLen = Math.floor(index / 0xffff); - const prefix = String.fromCharCode(0xffff).repeat(prefixLen); - return `${prefix}${String.fromCharCode(index - prefixLen * 0xffff)}`; -} diff --git a/packages/dds/merge-tree/src/partialLengths.ts b/packages/dds/merge-tree/src/partialLengths.ts index 16676a0056cf..c2a4d74a1adc 100644 --- a/packages/dds/merge-tree/src/partialLengths.ts +++ b/packages/dds/merge-tree/src/partialLengths.ts @@ -3,22 +3,27 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { Property, RedBlackTree } from "./collections"; import { UnassignedSequenceNumber } from "./constants"; +import { MergeTree } from "./mergeTree"; import { + // eslint-disable-next-line import/no-deprecated CollaborationWindow, compareNumbers, IMergeBlock, + IMergeNode, + IMoveInfo, IRemovalInfo, ISegment, + toMoveInfo, seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; +// eslint-disable-next-line import/no-deprecated import { SortedSet } from "./sortedSet"; +// eslint-disable-next-line import/no-deprecated class PartialSequenceLengthsSet extends SortedSet { protected getKey(item: PartialSequenceLength): number { return item.seq; @@ -47,6 +52,12 @@ class PartialSequenceLengthsSet extends SortedSet super.addOrUpdate(newItem, (currentPartial, partialLength) => { currentPartial.seglen += partialLength.seglen; + + if (partialLength.remoteObliteratedLen) { + currentPartial.remoteObliteratedLen ??= 0; + currentPartial.remoteObliteratedLen += partialLength.remoteObliteratedLen; + } + currentPartial.len += partialLength.seglen; combineOverlapClients(currentPartial, partialLength); }); @@ -123,6 +134,11 @@ export interface PartialSequenceLength { * clientId for the client that submitted the op with sequence number `seq`. */ clientId?: number; + /** + * If this partial length obliterated remote segments, this is the length of + * those segments + */ + remoteObliteratedLen?: number; /** * This field maps each client to the size of the intersection between segments deleted at this seq * and segments concurrently deleted by that client. @@ -144,12 +160,17 @@ export interface PartialSequenceLength { * corresponds to an op submitted by client 0 which: * - reduces the length of this block by 10 (it may have deleted a single segment of length 10, * several segments totalling length 10, or even delete and add content for a total reduction of 10 length) - * - was concurrent to an op submitted by client 1 that also removed some of the same segments, + * - was concurrent to one or more ops submitted by client 1 that also removed some of the same segments, * whose length totalled 5 - * - was concurrent to an op submitted by client 3 that removed some of the same segments, + * - was concurrent to one or more ops submitted by client 3 that removed some of the same segments, * whose length totalled 10 */ overlapRemoveClients?: RedBlackTree; + /** + * This field is the same as `overlapRemoveClients`, except that it tracks + * overlapping obliterates rather than removes. + */ + overlapObliterateClients?: RedBlackTree; } interface UnsequencedPartialLengthInfo { @@ -191,6 +212,13 @@ interface LocalPartialSequenceLength extends PartialSequenceLength { export interface PartialSequenceLengthsOptions { verifier?: (partialLengths: PartialSequenceLengths) => void; + verifyExpected?: ( + mergeTree: MergeTree, + node: IMergeBlock, + refSeq: number, + clientId: number, + localSeq?: number, + ) => void; zamboni: boolean; } @@ -258,6 +286,7 @@ export class PartialSequenceLengths { */ public static combine( block: IMergeBlock, + // eslint-disable-next-line import/no-deprecated collabWindow: CollaborationWindow, recur = false, computeLocalPartials = false, @@ -346,6 +375,7 @@ export class PartialSequenceLengths { */ private static fromLeaves( block: IMergeBlock, + // eslint-disable-next-line import/no-deprecated collabWindow: CollaborationWindow, computeLocalPartials: boolean, ): PartialSequenceLengths { @@ -366,16 +396,20 @@ export class PartialSequenceLengths { PartialSequenceLengths.insertSegment(combinedPartialLengths, segment); } const removalInfo = toRemovalInfo(segment); + const moveInfo = toMoveInfo(segment); if ( - removalInfo?.removedSeq !== undefined && - seqLTE(removalInfo.removedSeq, collabWindow.minSeq) + (removalInfo?.removedSeq !== undefined && + seqLTE(removalInfo.removedSeq, collabWindow.minSeq)) || + (moveInfo?.movedSeq !== undefined && + seqLTE(moveInfo.movedSeq, collabWindow.minSeq)) ) { combinedPartialLengths.minLength -= segment.cachedLength; - } else if (removalInfo !== undefined) { + } else if (removalInfo !== undefined || moveInfo !== undefined) { PartialSequenceLengths.insertSegment( combinedPartialLengths, segment, removalInfo, + moveInfo, ); } } @@ -434,38 +468,238 @@ export class PartialSequenceLengths { } } + private static accumulateMoveClientOverlap( + partialLength: PartialSequenceLength, + overlapMoveClientIds: number[], + seglen: number, + ) { + if (partialLength.overlapObliterateClients) { + for (const clientId of overlapMoveClientIds) { + const overlapClientNode = partialLength.overlapObliterateClients.get(clientId); + if (!overlapClientNode) { + partialLength.overlapObliterateClients.put(clientId, { clientId, seglen }); + } else { + overlapClientNode.data.seglen += seglen; + } + } + } else { + partialLength.overlapObliterateClients = PartialSequenceLengths.getOverlapClients( + overlapMoveClientIds, + seglen, + ); + } + } + + /** + * Coalesce overlapping move lengths for a partial length entry that already + * exists + * + * @param segmentLen - Length of segment with overlapping moves + * @param segment - Segment with overlapping moves + * @param firstGte - Existing partial length entry + * @param clientIds - Ids of clients that concurrently obliterated this segment + */ + static accumulateMoveOverlapForExisting( + segmentLen: number, + segment: ISegment, + firstGte: PartialSequenceLength, + clientIds: number[], + ) { + const nonInsertingClientIds = clientIds.filter((id) => id !== segment.clientId); + + PartialSequenceLengths.accumulateMoveClientOverlap( + firstGte, + nonInsertingClientIds, + segmentLen, + ); + + // if this segment was obliterated by the client that inserted it, + // and if it overlaps with the obliterate of another client, we need to + // take into account whether it was obliterated on insert by the other + // client + if (clientIds.length !== nonInsertingClientIds.length) { + PartialSequenceLengths.accumulateMoveClientOverlap( + firstGte, + [segment.clientId], + segment.wasMovedOnInsert ? -segment.cachedLength : segmentLen, + ); + } + } + /** - * Inserts length information about the insertion of `segment` into `combinedPartialLengths.partialLengths`. + * @param obliterateOverlapLen - Length of segment with overlap + * @param clientIds - Ids of clients that have concurrently obliterated this + * segment + */ + private static getMoveOverlapForExisting( + segment: ISegment, + obliterateOverlapLen: number, + clientIds: number[], + ): RedBlackTree { + const nonInsertingClientIds = clientIds.filter((id) => id !== segment.clientId); + const overlapObliterateClients = PartialSequenceLengths.getOverlapClients( + nonInsertingClientIds, + obliterateOverlapLen, + ); + + if (clientIds.length !== nonInsertingClientIds.length) { + overlapObliterateClients.put(segment.clientId, { + clientId: segment.clientId, + seglen: segment.wasMovedOnInsert ? -segment.cachedLength : obliterateOverlapLen, + }); + } + + return overlapObliterateClients; + } + + private static updatePartialsAfterInsertion( + segment: ISegment, + segmentLen: number, + remoteObliteratedLen: number | undefined, + obliterateOverlapLen: number = segmentLen, + partials: PartialSequenceLengthsSet, + seq: number, + clientId: number, + removeClientOverlap: number[] | undefined, + moveClientOverlap: number[] | undefined, + ) { + const firstGte = partials.firstGte(seq); + + let partialLengthEntry: PartialSequenceLength; + if (firstGte?.seq === seq) { + partialLengthEntry = firstGte; + // Existing entry at this seq--this occurs for ops that insert/delete + // more than one segment. + partialLengthEntry.seglen += segmentLen; + if (remoteObliteratedLen) { + partialLengthEntry.remoteObliteratedLen ??= 0; + partialLengthEntry.remoteObliteratedLen += remoteObliteratedLen; + } + if (removeClientOverlap) { + PartialSequenceLengths.accumulateRemoveClientOverlap( + firstGte, + removeClientOverlap, + obliterateOverlapLen, + ); + } + + if (moveClientOverlap) { + PartialSequenceLengths.accumulateMoveOverlapForExisting( + obliterateOverlapLen, + segment, + firstGte, + moveClientOverlap, + ); + } + } else { + const overlapObliterateClients = moveClientOverlap + ? PartialSequenceLengths.getMoveOverlapForExisting( + segment, + obliterateOverlapLen, + moveClientOverlap, + ) + : undefined; + + partialLengthEntry = { + seq, + clientId, + len: 0, + seglen: segmentLen, + remoteObliteratedLen, + overlapRemoveClients: removeClientOverlap + ? PartialSequenceLengths.getOverlapClients( + removeClientOverlap, + obliterateOverlapLen, + ) + : undefined, + overlapObliterateClients, + }; + + partials.addOrUpdate(partialLengthEntry); + } + } + + /** + * Inserts length information about the insertion of `segment` into + * `combinedPartialLengths.partialLengths`. + * * Does not update the clientSeqNumbers field to account for this segment. - * If `removalInfo` is defined, this operation updates the bookkeeping to account for the removal of this - * segment at the removedSeq instead. - * When the insertion or removal of the segment is un-acked and `combinedPartialLengths` is meant to compute - * such records, this does the analogous addition to the bookkeeping for the local segment in + * + * If `removalInfo` or `moveInfo` are defined, this operation updates the + * bookkeeping to account for the (re)moval of this segment at the (re)movedSeq + * instead. + * + * When the insertion or (re)moval of the segment is un-acked and + * `combinedPartialLengths` is meant to compute such records, this does the + * analogous addition to the bookkeeping for the local segment in * `combinedPartialLengths.unsequencedRecords`. */ private static insertSegment( combinedPartialLengths: PartialSequenceLengths, segment: ISegment, removalInfo?: IRemovalInfo, + moveInfo?: IMoveInfo, ) { + const removalIsLocal = !!removalInfo && removalInfo.removedSeq === UnassignedSequenceNumber; + const moveIsLocal = !!moveInfo && moveInfo.movedSeq === UnassignedSequenceNumber; const isLocal = - (removalInfo === undefined && segment.seq === UnassignedSequenceNumber) || - (removalInfo !== undefined && segment.removedSeq === UnassignedSequenceNumber); + segment.seq === UnassignedSequenceNumber || + (!!removalInfo && removalIsLocal && (!moveInfo || moveIsLocal)) || + (!!moveInfo && moveIsLocal && (!removalInfo || removalIsLocal)); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let seqOrLocalSeq = isLocal ? segment.localSeq! : segment.seq!; let segmentLen = segment.cachedLength; let clientId = segment.clientId; let removeClientOverlap: number[] | undefined; + let moveClientOverlap: number[] | undefined; + let remoteObliteratedLen: number | undefined; - if (removalInfo) { + // it's not possible to have an overlapping obliterate and remove that are both local + assert( + (!moveIsLocal && !removalIsLocal) || moveIsLocal !== removalIsLocal, + "overlapping local obliterate and remove", + ); + + const removeHappenedFirst = + removalInfo && + (!moveInfo || + moveIsLocal || + (!removalIsLocal && moveInfo.movedSeq > removalInfo.removedSeq)); + + if (removeHappenedFirst) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - seqOrLocalSeq = isLocal ? removalInfo.localRemovedSeq! : removalInfo.removedSeq; + seqOrLocalSeq = removalIsLocal ? removalInfo.localRemovedSeq! : removalInfo.removedSeq; segmentLen = -segmentLen; // The client who performed the remove is always stored // in the first position of removalInfo. clientId = removalInfo.removedClientIds[0]; const hasOverlap = removalInfo.removedClientIds.length > 1; removeClientOverlap = hasOverlap ? removalInfo.removedClientIds : undefined; + } else if (moveInfo) { + // The client who performed the move is always stored + // in the first position of moveInfo. + clientId = moveInfo.movedClientIds[0]; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + seqOrLocalSeq = moveIsLocal ? moveInfo.localMovedSeq! : moveInfo.movedSeq; + + if (segment.wasMovedOnInsert) { + assert( + moveInfo.movedSeq !== -1, + "wasMovedOnInsert should only be set on acked obliterates", + ); + segmentLen = 0; + } else { + segmentLen = -segmentLen; + } + + const hasOverlap = moveInfo.movedClientIds.length > 1; + moveClientOverlap = hasOverlap ? moveInfo.movedClientIds : undefined; + } else if (segment.wasMovedOnInsert) { + // if this segment was obliterated on insert, its length is only + // visible to the client that inserted it + segmentLen = 0; + remoteObliteratedLen = segment.cachedLength; } const partials = isLocal @@ -476,34 +710,63 @@ export class PartialSequenceLengths { return; } - const firstGte = partials.firstGte(seqOrLocalSeq); + // overlapping move and remove, remove happened first + if (moveInfo && removalInfo && removeHappenedFirst && !moveIsLocal) { + // The client who performed the remove is always stored + // in the first position of removalInfo. + const moveClientId = moveInfo.movedClientIds[0]; + const hasOverlap = moveInfo.movedClientIds.length > 1; + + PartialSequenceLengths.updatePartialsAfterInsertion( + segment, + 0, + -segment.cachedLength, + segmentLen, + partials, + moveInfo.movedSeq, + moveClientId, + undefined, + hasOverlap ? moveInfo.movedClientIds : undefined, + ); + } - let partialLengthEntry: PartialSequenceLength; - if (firstGte?.seq === seqOrLocalSeq) { - partialLengthEntry = firstGte; - // Existing entry at this seq--this occurs for ops that insert/delete more than one segment. - partialLengthEntry.seglen += segmentLen; - if (removeClientOverlap) { - PartialSequenceLengths.accumulateRemoveClientOverlap( - firstGte, - removeClientOverlap, - segmentLen, - ); - } - } else { - partialLengthEntry = { - seq: seqOrLocalSeq, - clientId, - len: 0, - seglen: segmentLen, - overlapRemoveClients: removeClientOverlap - ? PartialSequenceLengths.getOverlapClients(removeClientOverlap, segmentLen) - : undefined, - }; + if (removalInfo && !removeHappenedFirst && !removalIsLocal) { + const removeSeqOrLocalSeq = removalIsLocal + ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + removalInfo.localRemovedSeq! + : removalInfo.removedSeq; + // The client who performed the remove is always stored + // in the first position of removalInfo. + const removeClientId = removalInfo.removedClientIds[0]; + const hasOverlap = removalInfo.removedClientIds.length > 1; - partials.addOrUpdate(partialLengthEntry); + PartialSequenceLengths.updatePartialsAfterInsertion( + segment, + 0, + -segment.cachedLength, + segmentLen, + partials, + removeSeqOrLocalSeq, + removeClientId, + hasOverlap ? removalInfo.removedClientIds : undefined, + undefined, + ); } + PartialSequenceLengths.updatePartialsAfterInsertion( + segment, + segmentLen, + remoteObliteratedLen, + undefined, + partials, + seqOrLocalSeq, + clientId, + removeClientOverlap, + moveClientOverlap, + ); + + // todo: the below block needs to be changed to handle obliterate, which + // doesn't have great support for reconnect at the moment. see ADO #3714 const { unsequencedRecords } = combinedPartialLengths; if (unsequencedRecords && removeClientOverlap && segment.localRemovedSeq !== undefined) { const localSeq = segment.localRemovedSeq; @@ -546,6 +809,7 @@ export class PartialSequenceLengths { partialLengths: PartialSequenceLengthsSet, seq: number, seqSeglen: number, + remoteObliteratedLen?: number, clientId?: number, ) { let seqPartialLen: PartialSequenceLength | undefined; @@ -569,9 +833,11 @@ export class PartialSequenceLengths { len, seglen: seqSeglen, seq, + remoteObliteratedLen, }; partialLengths.addOrUpdate(seqPartialLen); } else { + seqPartialLen.remoteObliteratedLen = remoteObliteratedLen; seqPartialLen.seglen = seqSeglen; seqPartialLen.len = len; // Assert client id matches @@ -639,9 +905,11 @@ export class PartialSequenceLengths { node: IMergeBlock, seq: number, clientId: number, + // eslint-disable-next-line import/no-deprecated collabWindow: CollaborationWindow, ) { let seqSeglen = 0; + let remoteObliteratedLen = 0; let segCount = 0; // Compute length for seq across children for (let i = 0; i < node.childCount; i++) { @@ -652,22 +920,64 @@ export class PartialSequenceLengths { const branchPartialLengths = childBlock.partialLengths!; const partialLengths = branchPartialLengths.partialLengths; const leqPartial = partialLengths.latestLeq(seq); - if (leqPartial) { - if (leqPartial.seq === seq) { - seqSeglen += leqPartial.seglen; - } + if (leqPartial && leqPartial.seq === seq) { + seqSeglen += leqPartial.seglen; + remoteObliteratedLen += leqPartial.remoteObliteratedLen ?? 0; } segCount += branchPartialLengths.segmentCount; } else { const segment = child; const removalInfo = toRemovalInfo(segment); - - if (segment.seq === seq) { - if (removalInfo?.removedSeq !== seq) { + const moveInfo = toMoveInfo(segment); + + const removalIsLocal = + !!removalInfo && removalInfo.removedSeq === UnassignedSequenceNumber; + const moveIsLocal = !!moveInfo && moveInfo.movedSeq === UnassignedSequenceNumber; + + const removeHappenedFirst = + removalInfo && + (!moveInfo || + moveIsLocal || + (!removalIsLocal && moveInfo.movedSeq > removalInfo.removedSeq)); + + if (seq === segment.seq) { + // if this segment was moved on insert, its length should + // only be visible to the inserting client + if ( + segment.wasMovedOnInsert && + segment.seq !== undefined && + moveInfo && + moveInfo.movedSeq < segment.seq + ) { + remoteObliteratedLen += segment.cachedLength; + } else { seqSeglen += segment.cachedLength; } - } else { - if (removalInfo?.removedSeq === seq) { + } + + if (seq === removalInfo?.removedSeq) { + // if the remove op happened before an overlapping obliterate, + // all clients can see the remove at this seq. otherwise, only + // the removing client is aware of the remove + if (removeHappenedFirst) { + seqSeglen -= segment.cachedLength; + } else { + remoteObliteratedLen -= segment.cachedLength; + } + } + + if (seq === moveInfo?.movedSeq) { + if (removeHappenedFirst) { + remoteObliteratedLen -= segment.cachedLength; + } else if ( + segment.wasMovedOnInsert && + segment.seq !== UnassignedSequenceNumber && + segment.seq !== undefined && + moveInfo.movedSeq > segment.seq + ) { + remoteObliteratedLen += segment.cachedLength; + seqSeglen -= segment.cachedLength; + } else if (segment.seq !== UnassignedSequenceNumber) { seqSeglen -= segment.cachedLength; } } @@ -677,9 +987,21 @@ export class PartialSequenceLengths { this.segmentCount = segCount; this.unsequencedRecords = undefined; - PartialSequenceLengths.addSeq(this.partialLengths, seq, seqSeglen, clientId); + PartialSequenceLengths.addSeq( + this.partialLengths, + seq, + seqSeglen, + remoteObliteratedLen, + clientId, + ); this.clientSeqNumbers[clientId] ??= new PartialSequenceLengthsSet(); - PartialSequenceLengths.addSeq(this.clientSeqNumbers[clientId], seq, seqSeglen); + PartialSequenceLengths.addSeq( + this.clientSeqNumbers[clientId], + seq, + seqSeglen + remoteObliteratedLen, + undefined, + clientId, + ); if (PartialSequenceLengths.options.zamboni) { this.zamboni(collabWindow); } @@ -808,6 +1130,7 @@ export class PartialSequenceLengths { } // Clear away partial sums for sequence numbers earlier than the current window + // eslint-disable-next-line import/no-deprecated private zamboni(segmentWindow: CollaborationWindow) { this.minLength += this.partialLengths.copyDown(segmentWindow.minSeq); this.minSeq = segmentWindow.minSeq; @@ -828,8 +1151,9 @@ export class PartialSequenceLengths { // Assumes sequence number already coalesced and that this is called in increasing `seq` order. private addClientSeqNumberFromPartial(partialLength: PartialSequenceLength) { + const seglen = partialLength.seglen + (partialLength.remoteObliteratedLen ?? 0); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.addClientSeqNumber(partialLength.clientId!, partialLength.seq, partialLength.seglen); + this.addClientSeqNumber(partialLength.clientId!, partialLength.seq, seglen); if (partialLength.overlapRemoveClients) { partialLength.overlapRemoveClients.map((oc: Property) => { // Original client entry was handled above @@ -839,6 +1163,15 @@ export class PartialSequenceLengths { return true; }); } + if (partialLength.overlapObliterateClients) { + partialLength.overlapObliterateClients.map((oc: Property) => { + // Original client entry was handled above + if (partialLength.clientId !== oc.data.clientId) { + this.addClientSeqNumber(oc.data.clientId, partialLength.seq, oc.data.seglen); + } + return true; + }); + } } private cliLatestLEQ(clientId: number, refSeq: number): PartialSequenceLength | undefined { @@ -856,7 +1189,7 @@ function verifyPartialLengths( partialSeqLengths: PartialSequenceLengths, partialLengths: PartialSequenceLengthsSet, clientPartials: boolean, -) { +): number { if (partialLengths.size === 0) { return 0; } @@ -866,7 +1199,7 @@ function verifyPartialLengths( let count = 0; for (const partialLength of partialLengths.items) { - // Count total number of partial length + // Count total number of partial length entries count++; // Sequence number should be larger or equal to minseq @@ -913,20 +1246,67 @@ function verifyPartialLengths( 0x058 /* "Both overlapRemoveClients and clientPartials are set!" */, ); - // Each overlap client count as one, but the first remove to sequence was already counted. + // Each overlap client counts as one, but the first remove to sequence was already counted. // (this aligns with the logic to omit the removing client in `addClientSeqNumberFromPartial`) count += partialLength.overlapRemoveClients.size() - 1; } + + if (partialLength.overlapObliterateClients) { + // Only the flat partialLengths can have overlapObliterateClients, the per client view shouldn't + assert(!clientPartials, "Both overlapObliterateClients and clientPartials are set!"); + + // Each overlap client counts as one, but the first move to sequence was already counted. + // (this aligns with the logic to omit the moving client in `addClientSeqNumberFromPartial`) + count += partialLength.overlapObliterateClients.size() - 1; + } } return count; } +export function verifyExpected( + mergeTree: MergeTree, + node: IMergeBlock, + refSeq: number, + clientId: number, + localSeq?: number, +) { + if ( + (!mergeTree.collabWindow.collaborating || mergeTree.collabWindow.clientId === clientId) && + (node.isLeaf() || localSeq === undefined) + ) { + return; + } + + const partialLen = node.partialLengths?.getPartialLength(refSeq, clientId, localSeq); + + let expected = 0; + const nodesToVisit: IMergeNode[] = [node]; + + while (nodesToVisit.length > 0) { + const thisNode = nodesToVisit.pop(); + if (!thisNode) { + continue; + } + if (thisNode.isLeaf()) { + expected += mergeTree["nodeLength"](thisNode, refSeq, clientId, localSeq) ?? 0; + } else { + nodesToVisit.push(...thisNode.children.slice(0, thisNode.childCount)); + } + } + + if (expected !== partialLen) { + node.partialLengths?.getPartialLength(refSeq, clientId, localSeq); + throw new Error( + `expected partial length of ${expected} but found ${partialLen}. refSeq: ${refSeq}, clientId: ${clientId}`, + ); + } +} + export function verify(partialSeqLengths: PartialSequenceLengths) { if (partialSeqLengths["clientSeqNumbers"]) { - let cliCount = 0; for (const cliSeq of partialSeqLengths["clientSeqNumbers"]) { if (cliSeq) { - cliCount += verifyPartialLengths(partialSeqLengths, cliSeq, true); + verifyPartialLengths(partialSeqLengths, cliSeq, true); } } @@ -935,17 +1315,8 @@ export function verify(partialSeqLengths: PartialSequenceLengths) { !!partialSeqLengths["partialLengths"], 0x059 /* "Client view exists but flat view does not!" */, ); - const flatCount = verifyPartialLengths( - partialSeqLengths, - partialSeqLengths["partialLengths"], - false, - ); - // The number of partial lengths on the client view and flat view should be the same - assert( - flatCount === cliCount, - 0x05a /* "Mismatch between number of partial lengths on client and flat views!" */, - ); + verifyPartialLengths(partialSeqLengths, partialSeqLengths["partialLengths"], false); } else { // If we don't have a client view, we shouldn't have the flat view either assert( @@ -974,27 +1345,45 @@ function cloneOverlapRemoveClients( } /** - * Combines the `overlapRemoveClients` field of two `PartialSequenceLength` objects, - * modifying the first PartialSequenceLength's bookkeeping in-place. + * Combines the `overlapRemoveClients` and `overlapObliterateClients` fields of + * two `PartialSequenceLength` objects, modifying the first PartialSequenceLength's + * bookkeeping in-place. * * Combination is performed additively on `seglen` on a per-client basis. */ export function combineOverlapClients(a: PartialSequenceLength, b: PartialSequenceLength) { - const overlapRemoveClientsA = a.overlapRemoveClients; - if (overlapRemoveClientsA) { - if (b.overlapRemoveClients) { - b.overlapRemoveClients.map((bProp: Property) => { - const aProp = overlapRemoveClientsA.get(bProp.key); - if (aProp) { - aProp.data.seglen += bProp.data.seglen; - } else { - overlapRemoveClientsA.put(bProp.data.clientId, { ...bProp.data }); - } - return true; - }); + function combine( + treeA: RedBlackTree | undefined, + treeB: RedBlackTree | undefined, + ): RedBlackTree | undefined { + if (treeA) { + if (treeB) { + treeB.map((bProp: Property) => { + const aProp = treeA.get(bProp.key); + if (aProp) { + aProp.data.seglen += bProp.data.seglen; + } else { + treeA.put(bProp.data.clientId, { ...bProp.data }); + } + return true; + }); + } + } else { + return cloneOverlapRemoveClients(treeB); } - } else { - a.overlapRemoveClients = cloneOverlapRemoveClients(b.overlapRemoveClients); + } + + const overlapRemoveClients = combine(a.overlapRemoveClients, b.overlapRemoveClients); + if (overlapRemoveClients) { + a.overlapRemoveClients = overlapRemoveClients; + } + + const overlapObliterateClients = combine( + a.overlapObliterateClients, + b.overlapObliterateClients, + ); + if (overlapObliterateClients) { + a.overlapObliterateClients = overlapObliterateClients; } } @@ -1020,6 +1409,9 @@ function mergePartialLengths( mergedLengths.addOrUpdate({ ...partialLength, overlapRemoveClients: cloneOverlapRemoveClients(partialLength.overlapRemoveClients), + overlapObliterateClients: cloneOverlapRemoveClients( + partialLength.overlapObliterateClients, + ), }); } return mergedLengths; diff --git a/packages/dds/merge-tree/src/properties.ts b/packages/dds/merge-tree/src/properties.ts index 380b4a7f99af..d8a062a9c491 100644 --- a/packages/dds/merge-tree/src/properties.ts +++ b/packages/dds/merge-tree/src/properties.ts @@ -3,91 +3,27 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - -import { ICombiningOp } from "./ops"; - /** + * Any mapping from a string to values of type `T` * @alpha */ export interface MapLike { [index: string]: T; } -// We use any because when you include custom methods -// such as toJSON(), JSON.stringify accepts most types other -// than functions /** + * A loosely-typed mapping from strings to any value. + * + * @remarks Property sets are expected to be JSON-stringify-able. + * + * @privateRemarks PropertySet is typed using `any` because when you include + * custom methods such as toJSON(), JSON.stringify accepts most types other than + * functions * @alpha */ export type PropertySet = MapLike; -// Assume these are created with Object.create(null) - -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - * @internal - */ -export interface IConsensusValue { - seq: number; - value: any; -} - /** - * @deprecated This functionality was not intended for public export and will - * be removed in a future release. - * @internal - */ -export function combine( - combiningInfo: ICombiningOp, - currentValue: any, - newValue: any, - seq?: number, -) { - let _currentValue = currentValue; - - if (_currentValue === undefined) { - _currentValue = combiningInfo.defaultValue; - } - // Fixed set of operations for now - - switch (combiningInfo.name) { - case "incr": - _currentValue += newValue as number; - if (combiningInfo.minValue) { - if (_currentValue < combiningInfo.minValue) { - _currentValue = combiningInfo.minValue; - } - } - break; - case "consensus": - if (_currentValue === undefined) { - const cv: IConsensusValue = { - value: newValue, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - seq: seq!, - }; - - _currentValue = cv; - } else { - const cv = _currentValue as IConsensusValue; - if (cv.seq === -1) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - cv.seq = seq!; - } - } - break; - default: - break; - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return _currentValue; -} - -/** - * @deprecated This functionality was not intended for public export and will - * be removed in a future release. * @internal */ export function matchProperties(a: PropertySet | undefined, b: PropertySet | undefined) { @@ -122,12 +58,7 @@ export function matchProperties(a: PropertySet | undefined, b: PropertySet | und * be removed in a future release. * @internal */ -export function extend( - base: MapLike, - extension: MapLike | undefined, - combiningOp?: ICombiningOp, - seq?: number, -) { +export function extend(base: MapLike, extension: MapLike | undefined) { if (extension !== undefined) { // eslint-disable-next-line guard-for-in, no-restricted-syntax for (const key in extension) { @@ -136,10 +67,7 @@ export function extend( // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete base[key]; } else { - base[key] = - combiningOp && combiningOp.name !== "rewrite" - ? combine(combiningOp, base[key], v, seq) - : v; + base[key] = v; } } } @@ -171,17 +99,9 @@ export function clone(extension: MapLike | undefined) { * be removed in a future release. * @internal */ -export function addProperties( - oldProps: PropertySet | undefined, - newProps: PropertySet, - op?: ICombiningOp, - seq?: number, -) { - let _oldProps = oldProps; - if (!_oldProps || (op && op.name === "rewrite")) { - _oldProps = createMap(); - } - extend(_oldProps, newProps, op, seq); +export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet) { + const _oldProps = oldProps ?? createMap(); + extend(_oldProps, newProps); return _oldProps; } diff --git a/packages/dds/merge-tree/src/referencePositions.ts b/packages/dds/merge-tree/src/referencePositions.ts index ba3f11d3146d..d77c6e4278dd 100644 --- a/packages/dds/merge-tree/src/referencePositions.ts +++ b/packages/dds/merge-tree/src/referencePositions.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - -import { Stack } from "./collections"; import { SlidingPreference } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; -import { ReferenceType, ICombiningOp } from "./ops"; -import { PropertySet, MapLike } from "./properties"; +import { ReferenceType } from "./ops"; +import { PropertySet } from "./properties"; /** * @internal @@ -40,17 +37,6 @@ export const refGetTileLabels = (refPos: ReferencePosition): string[] | undefine ? (refPos.properties[reservedTileLabelsKey] as string[]) : undefined; -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - * @internal - */ -export const refGetRangeLabels = (refPos: ReferencePosition): string[] | undefined => - // eslint-disable-next-line no-bitwise - refTypeIncludesFlag(refPos, ReferenceType.NestBegin | ReferenceType.NestEnd) && - refPos.properties - ? (refPos.properties[reservedRangeLabelsKey] as string[]) - : undefined; - /** * @internal */ @@ -59,15 +45,6 @@ export function refHasTileLabel(refPos: ReferencePosition, label: string): boole return tileLabels?.includes(label) ?? false; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - * @internal - */ -export function refHasRangeLabel(refPos: ReferencePosition, label: string): boolean { - const rangeLabels = refGetRangeLabels(refPos); - return rangeLabels?.includes(label) ?? false; -} - /** * @internal */ @@ -75,14 +52,6 @@ export function refHasTileLabels(refPos: ReferencePosition): boolean { return refGetTileLabels(refPos) !== undefined; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - * @internal - */ -export function refHasRangeLabels(refPos: ReferencePosition): boolean { - return refGetRangeLabels(refPos) !== undefined; -} - /** * Represents a reference to a place within a merge tree. This place conceptually remains stable over time * by referring to a particular segment and offset within that segment. @@ -91,12 +60,15 @@ export function refHasRangeLabels(refPos: ReferencePosition): boolean { */ export interface ReferencePosition { /** - * @returns Properties associated with this reference + * Properties associated with this reference */ properties?: PropertySet; /** - * Defaults to forward + * The direction for this reference position to slide when the segment it + * points to is removed. See {@link (SlidingPreference:type)} for additional context. + * + * Defaults to SlidingPreference.Forward */ slidingPreference?: SlidingPreference; @@ -120,20 +92,13 @@ export interface ReferencePosition { /** * @param newProps - Properties to add to this reference. - * @param op - Combining semantics for changed properties. By default, property changes are last-write-wins. * @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. */ - addProperties(newProps: PropertySet, op?: ICombiningOp): void; + addProperties(newProps: PropertySet): void; isLeaf(): this is ISegment; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - * @alpha - */ -export type RangeStackMap = MapLike>; - /** * @internal */ diff --git a/packages/dds/merge-tree/src/revertibles.ts b/packages/dds/merge-tree/src/revertibles.ts index c30a5a70baa3..fdc1e8761169 100644 --- a/packages/dds/merge-tree/src/revertibles.ts +++ b/packages/dds/merge-tree/src/revertibles.ts @@ -3,11 +3,9 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert, unreachableCase } from "@fluidframework/core-utils"; import { UsageError } from "@fluidframework/telemetry-utils"; -import { List } from "./collections"; +import { DoublyLinkedList } from "./collections"; import { EndOfTreeSegment } from "./endOfTreeSegment"; import { LocalReferenceCollection, LocalReferencePosition } from "./localReference"; import { IMergeTreeDeltaCallbackArgs } from "./mergeTreeDeltaCallback"; @@ -64,9 +62,9 @@ interface RemoveSegmentRefProperties { * @alpha */ export interface MergeTreeRevertibleDriver { - insertFromSpec(pos: number, spec: IJSONSegment); - removeRange(start: number, end: number); - annotateRange(start: number, end: number, props: PropertySet); + insertFromSpec(pos: number, spec: IJSONSegment): void; + removeRange(start: number, end: number): void; + annotateRange(start: number, end: number, props: PropertySet): void; } /** @@ -297,7 +295,9 @@ function revertLocalRemove( (lref.properties as Partial)?.referenceSpace === "mergeTreeDeltaRevertible"; - const insertRef: Partial>> = {}; + const insertRef: Partial< + Record<"before" | "after", DoublyLinkedList> + > = {}; const forward = insertSegment.ordinal < refSeg.ordinal; const refHandler = (lref: LocalReferencePosition) => { // once we reach it keep the original reference where it is @@ -307,10 +307,10 @@ function revertLocalRemove( } if (localSlideFilter(lref)) { if (forward) { - const before = (insertRef.before ??= new List()); + const before = (insertRef.before ??= new DoublyLinkedList()); before.push(lref); } else { - const after = (insertRef.after ??= new List()); + const after = (insertRef.after ??= new DoublyLinkedList()); after.unshift(lref); } } diff --git a/packages/dds/merge-tree/src/segmentGroupCollection.ts b/packages/dds/merge-tree/src/segmentGroupCollection.ts index c0d30843477e..055f21aca413 100644 --- a/packages/dds/merge-tree/src/segmentGroupCollection.ts +++ b/packages/dds/merge-tree/src/segmentGroupCollection.ts @@ -3,19 +3,20 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - -import { List, walkList } from "./collections"; +import { DoublyLinkedList, walkList } from "./collections"; +// eslint-disable-next-line import/no-deprecated import { ISegment, SegmentGroup } from "./mergeTreeNodes"; /** * @alpha */ export class SegmentGroupCollection { - private readonly segmentGroups: List; + // eslint-disable-next-line import/no-deprecated + private readonly segmentGroups: DoublyLinkedList; constructor(private readonly segment: ISegment) { - this.segmentGroups = new List(); + // eslint-disable-next-line import/no-deprecated + this.segmentGroups = new DoublyLinkedList(); } public get size() { @@ -26,24 +27,18 @@ export class SegmentGroupCollection { return this.segmentGroups.empty; } - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ + // eslint-disable-next-line import/no-deprecated public enqueue(segmentGroup: SegmentGroup) { this.segmentGroups.push(segmentGroup); segmentGroup.segments.push(this.segment); } - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ + // eslint-disable-next-line import/no-deprecated public dequeue(): SegmentGroup | undefined { return this.segmentGroups.shift()?.data; } - /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ + // eslint-disable-next-line import/no-deprecated public pop?(): SegmentGroup | undefined { return this.segmentGroups.pop ? this.segmentGroups.pop()?.data : undefined; } @@ -54,6 +49,7 @@ export class SegmentGroupCollection { ); } + // eslint-disable-next-line import/no-deprecated private enqueueOnCopy(segmentGroup: SegmentGroup, sourceSegment: ISegment) { this.enqueue(segmentGroup); if (segmentGroup.previousProps) { diff --git a/packages/dds/merge-tree/src/segmentPropertiesManager.ts b/packages/dds/merge-tree/src/segmentPropertiesManager.ts index e38b42d01612..8900e1ab6a26 100644 --- a/packages/dds/merge-tree/src/segmentPropertiesManager.ts +++ b/packages/dds/merge-tree/src/segmentPropertiesManager.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { assert } from "@fluidframework/core-utils"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; -import { ICombiningOp, IMergeTreeAnnotateMsg } from "./ops"; -import { combine, createMap, MapLike, PropertySet } from "./properties"; +import { IMergeTreeAnnotateMsg } from "./ops"; +// eslint-disable-next-line import/no-deprecated +import { createMap, MapLike, PropertySet } from "./properties"; /** * @alpha @@ -20,15 +20,6 @@ export enum PropertiesRollback { /** Rollback */ Rollback, - - /** - * Rollback of a rewrite - * - * @deprecated We no longer intend to support this functionality and it will - * be removed in a future release. There is no replacement for this - * functionality. - */ - Rewrite, } /** @@ -36,27 +27,14 @@ export enum PropertiesRollback { */ export class PropertiesManager { private pendingKeyUpdateCount: MapLike | undefined; - private pendingRewriteCount: number; - - constructor() { - this.pendingRewriteCount = 0; - } public ackPendingProperties(annotateOp: IMergeTreeAnnotateMsg) { - const rewrite = annotateOp.combiningOp?.name === "rewrite"; - this.decrementPendingCounts(rewrite, annotateOp.props); + this.decrementPendingCounts(annotateOp.props); } - private decrementPendingCounts(rewrite: boolean, props: PropertySet) { - if (rewrite) { - this.pendingRewriteCount--; - } + private decrementPendingCounts(props: PropertySet) { for (const key of Object.keys(props)) { if (this.pendingKeyUpdateCount?.[key] !== undefined) { - if (rewrite && props[key] === null) { - // We don't track the pending count for this redundant case - continue; - } assert( this.pendingKeyUpdateCount[key] > 0, 0x05c /* "Trying to update more annotate props than do exist!" */, @@ -73,43 +51,23 @@ export class PropertiesManager { public addProperties( oldProps: PropertySet, newProps: PropertySet, - op?: ICombiningOp, seq?: number, collaborating: boolean = false, rollback: PropertiesRollback = PropertiesRollback.None, - ): PropertySet | undefined { + ): PropertySet { + // eslint-disable-next-line import/no-deprecated this.pendingKeyUpdateCount ??= createMap(); - // There are outstanding local rewrites, so block all non-local changes - if ( - this.pendingRewriteCount > 0 && - seq !== UnassignedSequenceNumber && - seq !== UniversalSequenceNumber && - collaborating - ) { - return undefined; - } - // Clean up counts for rolled back edits before modifying oldProps - if (collaborating) { - if (rollback === PropertiesRollback.Rollback) { - this.decrementPendingCounts(false, newProps); - } else if (rollback === PropertiesRollback.Rewrite) { - // oldProps is the correct props for tracking counts on rewrite because the ones in newProps include - // those that were implicitly cleared by the rewrite for which we don't track pending counts. - this.decrementPendingCounts(true, oldProps); - } + if (collaborating && rollback === PropertiesRollback.Rollback) { + this.decrementPendingCounts(newProps); } - const rewrite = op && op.name === "rewrite"; - const combiningOp = !rewrite ? (op ? op : undefined) : undefined; - const shouldModifyKey = (key: string): boolean => { if ( seq === UnassignedSequenceNumber || seq === UniversalSequenceNumber || - this.pendingKeyUpdateCount?.[key] === undefined || - combiningOp + this.pendingKeyUpdateCount?.[key] === undefined ) { return true; } @@ -117,30 +75,10 @@ export class PropertiesManager { }; const deltas: PropertySet = {}; - if (rewrite) { - if (collaborating && seq === UnassignedSequenceNumber) { - this.pendingRewriteCount++; - } - // We are re-writing so delete all the properties - // not in the new props - for (const key of Object.keys(oldProps)) { - if (!newProps[key] && shouldModifyKey(key)) { - deltas[key] = oldProps[key]; - - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete oldProps[key]; - } - } - } for (const key of Object.keys(newProps)) { if (collaborating) { if (seq === UnassignedSequenceNumber) { - if (rewrite && newProps[key] === null) { - // This case has already been handled above and - // we don't want to track the pending count for it in case of rollback - continue; - } if (this.pendingKeyUpdateCount?.[key] === undefined) { this.pendingKeyUpdateCount[key] = 0; } @@ -153,9 +91,7 @@ export class PropertiesManager { const previousValue: any = oldProps[key]; // The delta should be null if undefined, as that's how we encode delete deltas[key] = previousValue === undefined ? null : previousValue; - const newValue = combiningOp - ? combine(combiningOp, previousValue, undefined, seq) - : newProps[key]; + const newValue = newProps[key]; if (newValue === null) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete oldProps[key]; @@ -173,7 +109,7 @@ export class PropertiesManager { newManager: PropertiesManager, ): PropertySet | undefined { if (oldProps) { - // eslint-disable-next-line no-param-reassign + // eslint-disable-next-line no-param-reassign, import/no-deprecated newProps ??= createMap(); if (!newManager) { throw new Error("Must provide new PropertyManager"); @@ -181,7 +117,7 @@ export class PropertiesManager { for (const key of Object.keys(oldProps)) { newProps[key] = oldProps[key]; } - newManager.pendingRewriteCount = this.pendingRewriteCount; + // eslint-disable-next-line import/no-deprecated newManager.pendingKeyUpdateCount = createMap(); for (const key of Object.keys(this.pendingKeyUpdateCount!)) { newManager.pendingKeyUpdateCount[key] = this.pendingKeyUpdateCount![key]; @@ -191,7 +127,7 @@ export class PropertiesManager { } public hasPendingProperties() { - return this.pendingRewriteCount > 0 || Object.keys(this.pendingKeyUpdateCount!).length > 0; + return Object.keys(this.pendingKeyUpdateCount!).length > 0; } public hasPendingProperty(key: string): boolean { diff --git a/packages/dds/merge-tree/src/snapshotChunks.ts b/packages/dds/merge-tree/src/snapshotChunks.ts index 6c258c6bbe73..6fcfb3f2a8cc 100644 --- a/packages/dds/merge-tree/src/snapshotChunks.ts +++ b/packages/dds/merge-tree/src/snapshotChunks.ts @@ -67,6 +67,9 @@ export interface IJSONSegmentWithMergeInfo { seq?: number; removedClientIds?: string[]; removedSeq?: number; + movedClientIds?: string[]; + movedSeq?: number; + movedSeqs?: number[]; } /** diff --git a/packages/dds/merge-tree/src/snapshotLoader.ts b/packages/dds/merge-tree/src/snapshotLoader.ts index 8af07a8bf9b2..3fe60d6496c7 100644 --- a/packages/dds/merge-tree/src/snapshotLoader.ts +++ b/packages/dds/merge-tree/src/snapshotLoader.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { bufferToString } from "@fluid-internal/client-utils"; @@ -20,6 +19,7 @@ import { IChannelStorageService, } from "@fluidframework/datastore-definitions"; import { AttachState } from "@fluidframework/container-definitions"; +// eslint-disable-next-line import/no-deprecated import { Client } from "./client"; import { NonCollabClient, UniversalSequenceNumber } from "./constants"; import { ISegment } from "./mergeTreeNodes"; @@ -34,6 +34,7 @@ export class SnapshotLoader { constructor( private readonly runtime: IFluidDataStoreRuntime, + // eslint-disable-next-line import/no-deprecated private readonly client: Client, private readonly mergeTree: MergeTree, logger: ITelemetryLoggerExt, @@ -107,6 +108,12 @@ export class SnapshotLoader { if (spec.removedSeq !== undefined) { seg.removedSeq = spec.removedSeq; } + if (spec.movedSeq !== undefined) { + seg.movedSeq = spec.movedSeq; + } + if (spec.movedSeqs !== undefined) { + seg.movedSeqs = spec.movedSeqs; + } // this format had a bug where it didn't store all the overlap clients // this is for back compat, so we change the singular id to an array // this will only cause problems if there is an overlapping delete @@ -122,6 +129,11 @@ export class SnapshotLoader { this.client.getOrAddShortClientId(sid), ); } + if (spec.movedClientIds !== undefined) { + seg.movedClientIds = spec.movedClientIds?.map((sid) => + this.client.getOrAddShortClientId(sid), + ); + } } else { seg = this.client.specToSegment(spec); seg.seq = UniversalSequenceNumber; diff --git a/packages/dds/merge-tree/src/snapshotV1.ts b/packages/dds/merge-tree/src/snapshotV1.ts index 6f6bf4e842f2..bdace7227630 100644 --- a/packages/dds/merge-tree/src/snapshotV1.ts +++ b/packages/dds/merge-tree/src/snapshotV1.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { ITelemetryLoggerExt, createChildLogger } from "@fluidframework/telemetry-utils"; import { IFluidHandle } from "@fluidframework/core-interfaces"; import { IFluidSerializer } from "@fluidframework/shared-object-base"; @@ -218,8 +216,13 @@ export class SnapshotV1 { // there is a pending insert op that will deliver the segment on reconnection. // b) The segment was removed at or below the MSN. Pending ops can no longer reference this // segment, and therefore we can discard it. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (segment.seq === UnassignedSequenceNumber || segment.removedSeq! <= minSeq) { + if ( + segment.seq === UnassignedSequenceNumber || + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + segment.removedSeq! <= minSeq || + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + segment.movedSeq! <= minSeq + ) { return true; } @@ -230,7 +233,8 @@ export class SnapshotV1 { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion segment.seq! <= minSeq && // Segment is below the MSN, and... (segment.removedSeq === undefined || // .. Segment has not been removed, or... - segment.removedSeq === UnassignedSequenceNumber) // .. Removal op to be delivered on reconnect + segment.removedSeq === UnassignedSequenceNumber) && // .. Removal op to be delivered on reconnect + (segment.movedSeq === undefined || segment.movedSeq === UnassignedSequenceNumber) ) { // This segment is below the MSN, which means that future ops will not reference it. Attempt to // coalesce the new segment with the previous (if any). @@ -294,10 +298,27 @@ export class SnapshotV1 { ); } - // Sanity check that we are preserving either the seq < minSeq or a removed segment's info. + if (segment.movedSeq !== undefined) { + assert( + segment.movedSeq !== UnassignedSequenceNumber && segment.movedSeq > minSeq, + "On move info preservation, segment has invalid moved sequence number!", + ); + raw.movedSeq = segment.movedSeq; + raw.movedSeqs = segment.movedSeqs; + raw.movedClientIds = segment.movedClientIds?.map((id) => + this.getLongClientId(id), + ); + } + + // Sanity check that we are preserving either the seq > minSeq or a (re)moved segment's info. assert( (raw.seq !== undefined && raw.client !== undefined) || - (raw.removedSeq !== undefined && raw.removedClientIds !== undefined), + (raw.removedSeq !== undefined && raw.removedClientIds !== undefined) || + (raw.movedSeq !== undefined && + raw.movedClientIds !== undefined && + raw.movedClientIds.length > 0 && + raw.movedSeqs !== undefined && + raw.movedSeqs.length > 0), 0x066 /* "Corrupted preservation of segment metadata!" */, ); diff --git a/packages/dds/merge-tree/src/snapshotlegacy.ts b/packages/dds/merge-tree/src/snapshotlegacy.ts index 6e0d67e552c3..cadb39a4cb15 100644 --- a/packages/dds/merge-tree/src/snapshotlegacy.ts +++ b/packages/dds/merge-tree/src/snapshotlegacy.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { ITelemetryLoggerExt, createChildLogger } from "@fluidframework/telemetry-utils"; diff --git a/packages/dds/merge-tree/src/sortedSegmentSet.ts b/packages/dds/merge-tree/src/sortedSegmentSet.ts index 79ec77769e8c..1ff47bff38ce 100644 --- a/packages/dds/merge-tree/src/sortedSegmentSet.ts +++ b/packages/dds/merge-tree/src/sortedSegmentSet.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { LocalReferencePosition } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; +// eslint-disable-next-line import/no-deprecated import { SortedSet } from "./sortedSet"; /** @@ -31,6 +30,7 @@ export type SortedSegmentSetItem = * @deprecated This functionality was not meant to be exported and will be removed in a future release * @internal */ +// eslint-disable-next-line import/no-deprecated export class SortedSegmentSet extends SortedSet< T, string diff --git a/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts b/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts index 2b78379af9ee..d26e35b85daf 100644 --- a/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts +++ b/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts @@ -145,6 +145,7 @@ class TreeAttributionCollection implements IAttributionCollection { function runAnnotateVerificationTests() { it("attributes local property changes", () => { client.applyMsg(client.makeOpMessage(client.insertTextLocal(0, "123"), ++seq)); - const annotateOp = client.annotateRangeLocal(1, 2, { foo: 1 }, undefined); + const annotateOp = client.annotateRangeLocal(1, 2, { foo: 1 }); assert.deepEqual(client.getAllAttributionSeqs("foo"), [undefined, local, undefined]); client.applyMsg(client.makeOpMessage(annotateOp, ++seq)); client.applyMsg( - client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }, undefined), ++seq), + client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }), ++seq), ); assert.deepEqual(client.getAllAttributionSeqs("foo"), [undefined, 2, undefined]); }); @@ -63,7 +63,7 @@ describe("Attribution Policy", () => { it("uses LWW semantics for conflicting attribution of props", () => { client.applyMsg(client.makeOpMessage(client.insertTextLocal(0, "123"), ++seq)); - const localPropChange = client.annotateRangeLocal(1, 2, { foo: 1 }, undefined); + const localPropChange = client.annotateRangeLocal(1, 2, { foo: 1 }); client.annotateRangeRemote(0, 2, { foo: 2 }, ++seq, seq - 1, remoteUserLongId); const firstRemoteAnnotateSeq = seq; assert.equal(client.getPropertiesAtPosition(0)?.foo, 2); @@ -105,7 +105,7 @@ describe("Attribution Policy", () => { it("attributes annotation in a detached state", () => { client = new TestClient(client.mergeTree.options); client.insertTextLocal(0, "1", undefined); - client.annotateRangeLocal(0, 1, { foo: "bar" }, undefined); + client.annotateRangeLocal(0, 1, { foo: "bar" }); assert.deepEqual(client.getAllAttributionSeqs("foo"), [{ type: "detached", id: 0 }]); }); @@ -132,10 +132,10 @@ describe("Attribution Policy", () => { it("ignores local property changes", () => { client.applyMsg(client.makeOpMessage(client.insertTextLocal(0, "123"), ++seq)); client.applyMsg( - client.makeOpMessage(client.annotateRangeLocal(1, 2, { foo: 1 }, undefined), ++seq), + client.makeOpMessage(client.annotateRangeLocal(1, 2, { foo: 1 }), ++seq), ); client.applyMsg( - client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }, undefined), ++seq), + client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }), ++seq), ); assert.deepEqual(client.getAllAttributionSeqs(), [1, 1, 1]); }); @@ -233,10 +233,10 @@ describe("Attribution Policy", () => { it("attributes local property change on ack", () => { client.applyMsg(client.makeOpMessage(client.insertTextLocal(0, "123"), ++seq)); client.applyMsg( - client.makeOpMessage(client.annotateRangeLocal(1, 2, { foo: 1 }, undefined), ++seq), + client.makeOpMessage(client.annotateRangeLocal(1, 2, { foo: 1 }), ++seq), ); client.applyMsg( - client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }, undefined), ++seq), + client.makeOpMessage(client.annotateRangeLocal(0, 3, { bar: 2 }), ++seq), ); assert.deepEqual(client.getAllAttributionSeqs("foo"), [undefined, 2, undefined]); assert.deepEqual(client.getAllAttributionSeqs("bar"), [3, 3, 3]); diff --git a/packages/dds/merge-tree/src/test/beastTest.ts b/packages/dds/merge-tree/src/test/beastTest.ts index 14cc65ff2eb6..9656109bf08e 100644 --- a/packages/dds/merge-tree/src/test/beastTest.ts +++ b/packages/dds/merge-tree/src/test/beastTest.ts @@ -4,9 +4,9 @@ */ /* eslint-disable @typescript-eslint/consistent-type-assertions, no-bitwise */ -/* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-base-to-string */ import { strict as assert } from "assert"; import fs from "fs"; @@ -22,15 +22,15 @@ import { PropertyAction, RedBlackTree, SortedDictionary, - Stack, } from "../collections"; import { LocalClientId, UnassignedSequenceNumber, UniversalSequenceNumber } from "../constants"; import { IJSONMarkerSegment, IMergeNode, ISegment, - Marker, reservedMarkerIdKey, + compareNumbers, + compareStrings, } from "../mergeTreeNodes"; import { IMergeTreeDeltaOpArgs } from "../mergeTreeDeltaCallback"; import { createRemoveRangeOp } from "../opBuilder"; @@ -44,7 +44,6 @@ import { JsonSegmentSpecs } from "../snapshotChunks"; import { getStats, specToSegment, TestClient } from "./testClient"; import { TestServer } from "./testServer"; import { insertText, loadTextFromFile, nodeOrdinalsHaveIntegrity } from "./testUtils"; -import { ProxString, TST } from "./tst"; function LinearDictionary( compareKeys: KeyComparer, @@ -147,10 +146,6 @@ function log(message: any) { } } -const compareStrings = (a: string, b: string) => a.localeCompare(b); - -const compareNumbers = (a: number, b: number) => a - b; - function printStringProperty(p?: Property) { log(`[${p?.key}, ${p?.data}]`); return true; @@ -682,20 +677,6 @@ export function mergeTreeCheckedTest() { type SharedStringJSONSegment = IJSONTextSegment & IJSONMarkerSegment; -// enum AsyncRoundState { -// Insert, -// Remove, -// Tail -// } - -// interface AsyncRoundInfo { -// clientIndex: number; -// state: AsyncRoundState; -// insertSegmentCount?: number; -// removeSegmentCount?: number; -// iterIndex: number; -// } - export function TestPack(verbose = true) { const random = makeRandom(0xdeadbeef, 0xfeedbed); const minSegCount = 1; @@ -712,12 +693,8 @@ export function TestPack(verbose = true) { return str; } - const checkIncr = false; - let getTextTime = 0; let getTextCalls = 0; - let incrGetTextTime = 0; - let incrGetTextCalls = 0; const catchUpTime = 0; const catchUps = 0; @@ -738,17 +715,11 @@ export function TestPack(verbose = true) { client.accumOps ).toFixed(1); const aveGetTextTime = (getTextTime / getTextCalls).toFixed(1); - let aveIncrGetTextTime = "off"; let aveCatchUpTime = "off"; if (catchUps > 0) { aveCatchUpTime = (catchUpTime / catchUps).toFixed(1); } - if (checkIncr) { - aveIncrGetTextTime = (incrGetTextTime / incrGetTextCalls).toFixed(1); - } - log( - `get text time: ${aveGetTextTime} incr: ${aveIncrGetTextTime} catch up ${aveCatchUpTime}`, - ); + log(`get text time: ${aveGetTextTime} catch up ${aveCatchUpTime}`); log( `accum time ${client.accumTime} us ops: ${client.accumOps} ave time ${aveTime} - wtime ${adjTime} pack ${avePackTime} ave window ${aveWindow}`, ); @@ -770,8 +741,6 @@ export function TestPack(verbose = true) { const clientCount = 5; const fileSegCount = 0; let initString = ""; - const asyncExec = false; - const includeMarkers = false; if (!startFile) { initString = "don't ask for whom the bell tolls; it tolls for thee"; @@ -798,19 +767,10 @@ export function TestPack(verbose = true) { function checkTextMatch() { // log(`checking text match @${server.getCurrentSeq()}`); - let clockStart = clock(); + const clockStart = clock(); const serverText = server.getText(); getTextTime += elapsedMicroseconds(clockStart); getTextCalls++; - if (checkIncr) { - clockStart = clock(); - const serverIncrText = server.incrementalGetText(); - incrGetTextTime += elapsedMicroseconds(clockStart); - incrGetTextCalls++; - if (serverIncrText !== serverText) { - log("incr get text mismatch"); - } - } for (const client of clients) { const cliText = client.getText(); if (cliText !== serverText) { @@ -866,12 +826,6 @@ export function TestPack(verbose = true) { ); const preLen = client.getLength(); const pos = random.integer(0, preLen); - if (includeMarkers) { - const insertMarkerOp = client.insertMarkerLocal(pos, ReferenceType.Tile, { - [reservedTileLabelsKey]: "test", - }); - server.enqueueMsg(client.makeOpMessage(insertMarkerOp!, UnassignedSequenceNumber)); - } const insertTextOp = client.insertTextLocal(pos, text); server.enqueueMsg(client.makeOpMessage(insertTextOp!, UnassignedSequenceNumber)); @@ -917,74 +871,6 @@ export function TestPack(verbose = true) { let errorCount = 0; - // function asyncRoundStep(asyncInfo: AsyncRoundInfo, roundCount: number) { - // if (asyncInfo.state === AsyncRoundState.Insert) { - // if (!asyncInfo.insertSegmentCount) { - // asyncInfo.insertSegmentCount = randSmallSegmentCount(); - // } - // if (asyncInfo.clientIndex === clients.length) { - // asyncInfo.state = AsyncRoundState.Remove; - // asyncInfo.iterIndex = 0; - // } - // else { - // let client = clients[asyncInfo.clientIndex]; - // if (startFile) { - // randomWordMove(client); - // } - // else { - // randomSpateOfInserts(client, asyncInfo.iterIndex); - // } - // asyncInfo.iterIndex++; - // if (asyncInfo.iterIndex === asyncInfo.insertSegmentCount) { - // asyncInfo.clientIndex++; - // asyncInfo.insertSegmentCount = undefined; - // asyncInfo.iterIndex = 0; - // } - // } - // } - // if (asyncInfo.state === AsyncRoundState.Remove) { - // if (!asyncInfo.removeSegmentCount) { - // asyncInfo.removeSegmentCount = Math.floor(3 * asyncInfo.insertSegmentCount / 4); - // if (asyncInfo.removeSegmentCount < 1) { - // asyncInfo.removeSegmentCount = 1; - // } - // } - // if (asyncInfo.clientIndex === clients.length) { - // asyncInfo.state = AsyncRoundState.Tail; - // } - // else { - // let client = clients[asyncInfo.clientIndex]; - // if (startFile) { - // randomWordMove(client); - // } - // else { - // randomSpateOfInserts(client, asyncInfo.iterIndex); - // } - // asyncInfo.iterIndex++; - // if (asyncInfo.iterIndex === asyncInfo.removeSegmentCount) { - // asyncInfo.clientIndex++; - // asyncInfo.removeSegmentCount = undefined; - // asyncInfo.iterIndex = 0; - // } - // } - // } - // if (asyncInfo.state === AsyncRoundState.Tail) { - // finishRound(roundCount); - // } - // else { - // setImmediate(asyncRoundStep, asyncInfo, roundCount); - // } - // } - - // function asyncRound(roundCount: number) { - // let asyncInfo = { - // clientIndex: 0, - // iterIndex: 0, - // state: AsyncRoundState.Insert - // } - // setImmediate(asyncRoundStep, asyncInfo, roundCount); - // } - const extractSnapTime = 0; const extractSnapOps = 0; function finishRound(roundCount: number) { @@ -996,15 +882,6 @@ export function TestPack(verbose = true) { clientProcessSome(client, true); } - /* - if (checkTextMatch()) { - log(`round: ${i}`); - break; - } - */ - // log(server.getText()); - // log(server.mergeTree.toString()); - // log(getStats(server.mergeTree)); if (0 === roundCount % 100) { const clockStart = clock(); if (checkTextMatch()) { @@ -1079,11 +956,6 @@ export function TestPack(verbose = true) { randomWordMove(client); } else { randomSpateOfRemoves(client); - if (includeMarkers) { - if (client.getLength() > 200) { - randomSpateOfRemoves(client); - } - } } } if (serverProcessSome(server)) { @@ -1096,27 +968,15 @@ export function TestPack(verbose = true) { const startTime = Date.now(); let checkTime = 0; - let asyncRoundCount = 0; - function asyncStep() { - round(asyncRoundCount); - asyncRoundCount++; - if (asyncRoundCount < rounds) { - setImmediate(asyncStep); + for (let i = 0; i < rounds; i++) { + round(i); + if (errorCount > 0) { + break; } } + tail(); - if (asyncExec) { - setImmediate(asyncStep); - } else { - for (let i = 0; i < rounds; i++) { - round(i); - if (errorCount > 0) { - break; - } - } - tail(); - } function tail() { reportTiming(server); reportTiming(clients[2]); @@ -1488,12 +1348,6 @@ export function TestPack(verbose = true) { }; } -function compareProxStrings(a: ProxString, b: ProxString) { - const ascore = a.invDistance * 200 + a.val; - const bscore = b.invDistance * 200 + b.val; - return bscore - ascore; -} - const createLocalOpArgs = ( type: MergeTreeDeltaType, sequenceNumber: number, @@ -1504,106 +1358,6 @@ const createLocalOpArgs = ( } as ISequencedDocumentMessage, }); -function shuffle(a: T[]) { - let currentIndex = a.length; - let temp: T; - let randomIndex: number; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; - - // And swap it with the current element. - temp = a[currentIndex]; - a[currentIndex] = a[randomIndex]; - a[randomIndex] = temp; - } - - return a; -} - -function tst() { - const tree = new TST(); - const entries = [ - "giraffe", - "hut", - "aardvark", - "gold", - "hover", - "yurt", - "hot", - "antelope", - "gift", - "banana", - ]; - for (const entry of entries) { - tree.put(entry, true); - } - for (const entry of entries) { - log(`get ${entry}: ${tree.get(entry)}`); - } - const p1 = tree.keysWithPrefix("g"); - const p2 = tree.keysWithPrefix("gi"); - log(p1); - log(p2); - const p3 = tree.neighbors("hat"); - log(p3); - const ntree = new TST(); - const filename = path.join(__dirname, "../../public/literature/dict.txt"); - const content = fs.readFileSync(filename, "utf8"); - const splitContent = content.split(/\r\n|\n/g); - let corpusFilename = path.join(__dirname, "../../../public/literature/pp.txt"); - let corpusContent = fs.readFileSync(corpusFilename, "utf8"); - const corpusTree = new TST(); - function addCorpus(_corpusContent: string, _corpusTree: TST) { - let count = 0; - const re = /\b\w+\b/g; - let result: RegExpExecArray | null; - do { - result = re.exec(_corpusContent); - if (result) { - const candidate = result[0]; - count++; - const val = _corpusTree.get(candidate); - if (val !== undefined) { - _corpusTree.put(candidate, val + 1); - } else { - _corpusTree.put(candidate, 1); - } - } - } while (result); - return count; - } - const clockStart = clock(); - addCorpus(corpusContent, corpusTree); - corpusFilename = path.join(__dirname, "../../public/literature/shakespeare.txt"); - corpusContent = fs.readFileSync(corpusFilename, "utf8"); - addCorpus(corpusContent, corpusTree); - const a = shuffle(splitContent); - for (const entry of a) { - const freq = corpusTree.get(entry); - if (freq !== undefined) { - ntree.put(entry, freq); - } else { - ntree.put(entry, 1); - } - } - log(`size: ${ntree.size()}; random insert takes ${clockStart.trace().duration}ms`); - for (const entry of a) { - if (!ntree.get(entry)) { - log(`biff ${entry}`); - } - } - let p4 = ntree.neighbors("het").sort(compareProxStrings); - log(p4); - p4 = ntree.neighbors("peech").sort(compareProxStrings); - log(p4); - p4 = ntree.neighbors("tihs").sort(compareProxStrings); - log(p4); -} - export class RandomPack { random: IRandom; constructor() { @@ -1647,10 +1401,6 @@ export class RandomPack { } } -function docNodeToString(docNode: DocumentNode) { - return typeof docNode === "string" ? docNode : docNode.name; -} - export type DocumentNode = string | DocumentTree; /** * Generate and model documents from the following tree grammar: @@ -1684,7 +1434,6 @@ export class DocumentTree { }); this.pos++; } else { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands const trid = docNode.name + this.ids[docNode.name].toString(); docNode.id = trid; id = this.ids[docNode.name]++; @@ -1692,7 +1441,7 @@ export class DocumentTree { [reservedMarkerIdKey]: trid, [reservedRangeLabelsKey]: [docNode.name], }; - let behaviors = ReferenceType.NestBegin; + let behaviors = ReferenceType.Simple; if (docNode.name === "row") { props[reservedTileLabelsKey] = ["pg"]; behaviors |= ReferenceType.Tile; @@ -1706,7 +1455,7 @@ export class DocumentTree { } if (docNode.name !== "pg") { const etrid = `end-${docNode.name}${id?.toString()}`; - client.insertMarkerLocal(this.pos, ReferenceType.NestEnd, { + client.insertMarkerLocal(this.pos, ReferenceType.Simple, { [reservedMarkerIdKey]: etrid, [reservedRangeLabelsKey]: [docNode.name], }); @@ -1715,141 +1464,6 @@ export class DocumentTree { } } - checkStacksAllPositions(client: TestClient) { - let errorCount = 0; - let pos = 0; - const verbose = false; - const stacks = { - box: new Stack(), - row: new Stack(), - }; - - function printStack(stack: Stack) { - // eslint-disable-next-line @typescript-eslint/no-for-in-array, guard-for-in, no-restricted-syntax - for (const item in stack.items) { - log(item); - } - } - - function printStacks() { - for (const name of ["box", "row"]) { - log(`${name}:`); - printStack(stacks[name]); - } - } - - function checkTreeStackEmpty(treeStack: Stack) { - if (!treeStack.empty()) { - errorCount++; - log("mismatch: client stack empty; tree stack not"); - } - } - - const checkNodeStacks = (docNode: DocumentNode) => { - if (typeof docNode === "string") { - const text = docNode; - const epos = pos + text.length; - if (verbose) { - log(`stacks for [${pos}, ${epos}): ${text}`); - printStacks(); - } - const cliStacks = client.getStackContext(pos, ["box", "row"]); - for (const name of ["box", "row"]) { - const cliStack = cliStacks[name]; - const treeStack = >stacks[name]; - if (cliStack) { - const len = cliStack.items.length; - if (len > 0) { - if (len !== treeStack.items.length) { - log( - `stack length mismatch cli ${len} tree ${treeStack.items.length}`, - ); - errorCount++; - } - for (let i = 0; i < len; i++) { - const cliMarkerId = (cliStack.items[i] as Marker).getId(); - const treeMarkerId = treeStack.items[i]; - if (cliMarkerId !== treeMarkerId) { - errorCount++; - log( - `mismatch index ${i}: ${cliMarkerId} !== ${treeMarkerId} pos ${pos} text ${text}`, - ); - printStack(treeStack); - log(client.mergeTree.toString()); - } - } - } else { - checkTreeStackEmpty(treeStack); - } - } else { - checkTreeStackEmpty(treeStack); - } - } - pos = epos; - } else { - pos++; - if (docNode.name === "pg") { - checkNodeStacks(docNode.children[0]); - } else { - stacks[docNode.name].push(docNode.id); - for (const child of docNode.children) { - checkNodeStacks(child); - } - stacks[docNode.name].pop(); - pos++; - } - } - }; - - let prevPos = -1; - let prevChild: DocumentNode | undefined; - - // log(client.mergeTree.toString()); - for (const rootChild of this.children) { - if (prevPos >= 0) { - if (typeof prevChild !== "string" && prevChild?.name === "row") { - const id = prevChild.id; - const endId = `end-${id}`; - const endRowMarker = client.getMarkerFromId(endId); - const endRowPos = client.getPosition(endRowMarker); - prevPos = endRowPos; - } - const tilePos = client.findTile(prevPos + 1, "pg", false); - if (tilePos) { - if (tilePos.pos !== pos) { - errorCount++; - log( - `next tile ${tilePos.tile} found from pos ${prevPos} at ${tilePos.pos} compare to ${pos}`, - ); - } - } - } - if (verbose) { - log(`next child ${pos} with name ${docNodeToString(rootChild)}`); - } - prevPos = pos; - prevChild = rootChild; - // printStacks(); - checkNodeStacks(rootChild); - } - return errorCount; - } - - private generateClient() { - const client = new TestClient(); - client.startOrUpdateCollaboration("Fred"); - for (const child of this.children) { - this.addToMergeTree(client, child); - } - return client; - } - - static test1() { - const doc = DocumentTree.generateDocument(); - const client = doc.generateClient(); - return doc.checkStacksAllPositions(client); - } - static generateDocument() { const tree = new DocumentTree("Document", DocumentTree.generateContent(0.6)); return tree; @@ -1949,11 +1563,6 @@ function findReplacePerf(filename: string) { log(`${cFetches} fetches and ${cReplaces} replaces took ${elapsed} microseconds`); } -const testTST = false; -if (testTST) { - tst(); -} - const baseDir = "../../src/test/literature"; const testTimeout = 60000; @@ -1967,10 +1576,6 @@ describe("Routerlicious", () => { testPack.firstTest(); }); - it("hierarchy", () => { - assert(DocumentTree.test1() === 0, logLines.join("\n")); - }).timeout(testTimeout); - it("randolicious", () => { const testPack = TestPack(false); assert(testPack.randolicious() === 0, logLines.join("\n")); diff --git a/packages/dds/merge-tree/src/test/client.applyMsg.spec.ts b/packages/dds/merge-tree/src/test/client.applyMsg.spec.ts index cd0c5326f855..374c577d59c8 100644 --- a/packages/dds/merge-tree/src/test/client.applyMsg.spec.ts +++ b/packages/dds/merge-tree/src/test/client.applyMsg.spec.ts @@ -56,14 +56,9 @@ describe("client.applyMsg", () => { case 2: case 3: { const pos2 = Math.max(Math.floor((len - pos1) / 3) - imod6 + pos1, pos1 + 1); - const op = client.annotateRangeLocal( - pos1, - pos2, - { - foo: `${i}`, - }, - undefined, - ); + const op = client.annotateRangeLocal(pos1, pos2, { + foo: `${i}`, + }); const msg = client.makeOpMessage(op, i + 1); changes.set(i, { msg, segmentGroup: client.peekPendingSegmentGroups() }); break; @@ -148,7 +143,7 @@ describe("client.applyMsg", () => { const props = { foo: "bar", }; - const op = client.annotateRangeLocal(0, 1, props, undefined); + const op = client.annotateRangeLocal(0, 1, props); assert.equal(client.mergeTree.pendingSegments?.length, 1); @@ -167,7 +162,7 @@ describe("client.applyMsg", () => { foo: "bar", }; - const annotateOp = client.annotateRangeLocal(start, end, props, undefined); + const annotateOp = client.annotateRangeLocal(start, end, props); assert.equal(client.mergeTree.pendingSegments?.length, 1); @@ -196,7 +191,7 @@ describe("client.applyMsg", () => { end: annotateEnd, foo: "bar", }; - const annotateOp = client.annotateRangeLocal(0, annotateEnd, props, undefined); + const annotateOp = client.annotateRangeLocal(0, annotateEnd, props); messages.push(client.makeOpMessage(annotateOp, ++sequenceNumber)); @@ -492,12 +487,7 @@ describe("client.applyMsg", () => { const insertOp = clientA.makeOpMessage(clientA.insertTextLocal(0, "AAA"), ++seq); [clientA, clientB].map((c) => c.applyMsg(insertOp)); - const annotateOp = clientA.annotateRangeLocal( - 0, - clientA.getLength(), - { client: "A" }, - undefined, - )!; + const annotateOp = clientA.annotateRangeLocal(0, clientA.getLength(), { client: "A" })!; const seg = clientA.peekPendingSegmentGroups()!; const removeOp = clientB.makeOpMessage( diff --git a/packages/dds/merge-tree/src/test/client.applyStashedOpFarm.spec.ts b/packages/dds/merge-tree/src/test/client.applyStashedOpFarm.spec.ts index 277b897b4331..f024f8bcd8e1 100644 --- a/packages/dds/merge-tree/src/test/client.applyStashedOpFarm.spec.ts +++ b/packages/dds/merge-tree/src/test/client.applyStashedOpFarm.spec.ts @@ -36,7 +36,6 @@ function applyMessagesWithReconnect( if (messageData[0].clientId !== clients[1].longClientId) { const index = clients .map((c) => c.longClientId) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion .indexOf(messageData[0].clientId as string); const localMetadata = stashClients[index].applyStashedOp( messageData[0].contents as IMergeTreeOp, diff --git a/packages/dds/merge-tree/src/test/client.attributionFarm.spec.ts b/packages/dds/merge-tree/src/test/client.attributionFarm.spec.ts index a3fa7003b031..725c95381f10 100644 --- a/packages/dds/merge-tree/src/test/client.attributionFarm.spec.ts +++ b/packages/dds/merge-tree/src/test/client.attributionFarm.spec.ts @@ -22,7 +22,7 @@ import { TestClient } from "./testClient"; import { TestClientLogger } from "./testClientLogger"; export const annotateRange: TestOperation = (client: TestClient, opStart: number, opEnd: number) => - client.annotateRangeLocal(opStart, opEnd, { trackedProp: client.longClientId }, undefined); + client.annotateRangeLocal(opStart, opEnd, { trackedProp: client.longClientId }); const defaultOptions: Record<"initLen" | "modLen", IConfigRange> & IMergeTreeOperationRunnerConfig = { diff --git a/packages/dds/merge-tree/src/test/client.localReference.spec.ts b/packages/dds/merge-tree/src/test/client.localReference.spec.ts index 7f0567b426a9..a22aaf56fd33 100644 --- a/packages/dds/merge-tree/src/test/client.localReference.spec.ts +++ b/packages/dds/merge-tree/src/test/client.localReference.spec.ts @@ -11,9 +11,10 @@ import { toRemovalInfo } from "../mergeTreeNodes"; import { MergeTreeDeltaType, ReferenceType } from "../ops"; import { TextSegment } from "../textSegment"; import { DetachedReferencePosition } from "../referencePositions"; -import { LocalReferencePosition, SlidingPreference } from "../localReference"; +import { setValidateRefCount, LocalReferencePosition, SlidingPreference } from "../localReference"; import { getSlideToSegoff } from "../mergeTree"; import { createClientsAtInitialState } from "./testClientLogger"; +import { validateRefCount } from "./testUtils"; import { TestClient } from "./"; function getSlideOnRemoveReferencePosition( @@ -30,6 +31,14 @@ function getSlideOnRemoveReferencePosition( } describe("MergeTree.Client", () => { + beforeEach(() => { + setValidateRefCount(validateRefCount); + }); + + afterEach(() => { + setValidateRefCount(undefined); + }); + it("Remove segment of non-sliding local reference", () => { const client1 = new TestClient(); const client2 = new TestClient(); @@ -314,7 +323,7 @@ describe("MergeTree.Client", () => { client1.localReferencePositionToPosition(c1LocalRef), DetachedReferencePosition, ); - assert.notEqual(c1LocalRef.getSegment(), undefined); + assert.equal(c1LocalRef.getSegment(), undefined); }); it("References can have offsets on removed segment", () => { @@ -619,4 +628,82 @@ describe("MergeTree.Client", () => { assert.equal(client2.getText(), "aghidef"); assert.equal(client2.localReferencePositionToPosition(localRef), 0); }); + + it("doesn't crash for remove ref then link to undefined", () => { + const client1 = new TestClient(); + const client2 = new TestClient(); + + client1.startOrUpdateCollaboration("1"); + client2.startOrUpdateCollaboration("2"); + + let seq = 0; + const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "abcdef"), ++seq); + client1.applyMsg(insert1); + client2.applyMsg(insert1); + + const segInfo = client1.getContainingSegment(3); + + assert(segInfo.segment); + + const localRef = client1.createLocalReferencePosition( + segInfo.segment, + segInfo.offset, + ReferenceType.SlideOnRemove, + undefined, + ); + + assert.equal(localRef.getSegment(), segInfo.segment); + + assert(segInfo.segment.localRefs); + assert(!segInfo.segment.localRefs.empty); + + segInfo.segment.localRefs.removeLocalRef(localRef); + assert(segInfo.segment.localRefs.empty); + (localRef as any).link(undefined, 0, undefined); + assert(segInfo.segment.localRefs.empty); + + assert.equal(segInfo.segment.localRefs.empty, true); + assert.equal(segInfo.segment.localRefs.has(localRef), false); + assert.equal(localRef.getSegment(), undefined); + assert.equal(localRef.getOffset(), 0); + }); + + it("doesn't crash for link to undefined then remove ref", () => { + const client1 = new TestClient(); + const client2 = new TestClient(); + + client1.startOrUpdateCollaboration("1"); + client2.startOrUpdateCollaboration("2"); + + let seq = 0; + const insert1 = client1.makeOpMessage(client1.insertTextLocal(0, "abcdef"), ++seq); + client1.applyMsg(insert1); + client2.applyMsg(insert1); + + const segInfo = client1.getContainingSegment(3); + + assert(segInfo.segment); + + const localRef = client1.createLocalReferencePosition( + segInfo.segment, + segInfo.offset, + ReferenceType.SlideOnRemove, + undefined, + ); + + assert.equal(localRef.getSegment(), segInfo.segment); + + assert(segInfo.segment.localRefs); + assert(!segInfo.segment.localRefs.empty); + + (localRef as any).link(undefined, 0, undefined); + assert(segInfo.segment.localRefs.empty); + segInfo.segment.localRefs.removeLocalRef(localRef); + assert(segInfo.segment.localRefs.empty); + + assert.equal(segInfo.segment.localRefs.empty, true); + assert.equal(segInfo.segment.localRefs.has(localRef), false); + assert.equal(localRef.getSegment(), undefined); + assert.equal(localRef.getOffset(), 0); + }); }); diff --git a/packages/dds/merge-tree/src/test/client.localReferenceFarm.spec.ts b/packages/dds/merge-tree/src/test/client.localReferenceFarm.spec.ts index 09e56ea9c3f7..39987770020f 100644 --- a/packages/dds/merge-tree/src/test/client.localReferenceFarm.spec.ts +++ b/packages/dds/merge-tree/src/test/client.localReferenceFarm.spec.ts @@ -8,7 +8,7 @@ import { strict as assert } from "assert"; import { makeRandom } from "@fluid-private/stochastic-test-utils"; import { ReferencePosition } from "../referencePositions"; import { ReferenceType } from "../ops"; -import { SlidingPreference } from "../localReference"; +import { setValidateRefCount, SlidingPreference } from "../localReference"; import { IMergeTreeOperationRunnerConfig, removeRange, @@ -19,6 +19,7 @@ import { } from "./mergeTreeOperationRunner"; import { TestClient } from "./testClient"; import { TestClientLogger } from "./testClientLogger"; +import { validateRefCount } from "./testUtils"; const defaultOptions: Record<"initLen" | "modLen", IConfigRange> & IMergeTreeOperationRunnerConfig = { @@ -31,6 +32,14 @@ const defaultOptions: Record<"initLen" | "modLen", IConfigRange> & IMergeTreeOpe }; describe("MergeTree.Client", () => { + beforeEach(() => { + setValidateRefCount(validateRefCount); + }); + + afterEach(() => { + setValidateRefCount(undefined); + }); + // Generate a list of single character client names, support up to 69 clients const clientNames = generateClientNames(); diff --git a/packages/dds/merge-tree/src/test/client.reconnectFarm.spec.ts b/packages/dds/merge-tree/src/test/client.reconnectFarm.spec.ts index 0ab851e824e3..237a557d58bc 100644 --- a/packages/dds/merge-tree/src/test/client.reconnectFarm.spec.ts +++ b/packages/dds/merge-tree/src/test/client.reconnectFarm.spec.ts @@ -4,6 +4,7 @@ */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { strict as assert } from "assert"; import { IRandom, makeRandom, describeFuzz } from "@fluid-private/stochastic-test-utils"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { IMergeTreeOp } from "../ops"; @@ -40,12 +41,9 @@ function applyMessagesWithReconnect( // log and apply all the ops created in the round while (messageDatas.length > 0) { const [message, sg] = messageDatas.shift()!; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - if (reconnectingClientIds.includes(message.clientId as string)) { - reconnectClientMsgs - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - .get(message.clientId as string)! - .push([message.contents as IMergeTreeOp, sg]); + assert(message.clientId, "expected clientId to be defined"); + if (reconnectingClientIds.includes(message.clientId)) { + reconnectClientMsgs.get(message.clientId)!.push([message.contents as IMergeTreeOp, sg]); } else { message.sequenceNumber = ++seq; clients.forEach((c) => c.applyMsg(message)); diff --git a/packages/dds/merge-tree/src/test/client.replay.spec.ts b/packages/dds/merge-tree/src/test/client.replay.spec.ts index 9ac6195b8ef2..bfec366e3522 100644 --- a/packages/dds/merge-tree/src/test/client.replay.spec.ts +++ b/packages/dds/merge-tree/src/test/client.replay.spec.ts @@ -29,15 +29,13 @@ describe("MergeTree.Client", () => { originalClient.startOrUpdateCollaboration("A"); for (const group of file) { for (const msg of group.msgs) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - if (!msgClients.has(msg.clientId as string)) { + assert(msg.clientId, "expected clientId to be defined"); + if (!msgClients.has(msg.clientId)) { const client = await TestClient.createFromClientSnapshot( originalClient, - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - msg.clientId as string, + msg.clientId, ); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - msgClients.set(msg.clientId as string, { client, msgs: [] }); + msgClients.set(msg.clientId, { client, msgs: [] }); } } } @@ -48,8 +46,7 @@ describe("MergeTree.Client", () => { const initialText = logger.validate(); assert.strictEqual(initialText, group.initialText, "Initial text not as expected"); for (const msg of group.msgs) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const msgClient = msgClients.get(msg.clientId as string)!; + const msgClient = msgClients.get(msg.clientId!)!; while ( msgClient.msgs.length > 0 && msg.referenceSequenceNumber > msgClient.client.getCurrentSeq() diff --git a/packages/dds/merge-tree/src/test/client.rollback.spec.ts b/packages/dds/merge-tree/src/test/client.rollback.spec.ts index 3bdca1e712b8..68bcf999efda 100644 --- a/packages/dds/merge-tree/src/test/client.rollback.spec.ts +++ b/packages/dds/merge-tree/src/test/client.rollback.spec.ts @@ -100,7 +100,7 @@ describe("client.rollback", () => { [reservedMarkerIdKey]: "markerId", }); const marker = client.getMarkerFromId("markerId") as Marker; - client.annotateMarker(marker, { foo: "bar" }, undefined); + client.annotateMarker(marker, { foo: "bar" }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); const properties = marker.getProperties(); @@ -112,7 +112,7 @@ describe("client.rollback", () => { foo: "bar", }); const marker = client.getMarkerFromId("markerId") as Marker; - client.annotateMarker(marker, { foo: "baz" }, undefined); + client.annotateMarker(marker, { foo: "baz" }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); const properties = marker.getProperties(); @@ -124,7 +124,7 @@ describe("client.rollback", () => { foo: "bar", }); const marker = client.getMarkerFromId("markerId") as Marker; - client.annotateMarker(marker, { foo: null }, undefined); + client.annotateMarker(marker, { foo: null }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); const properties = marker.getProperties(); @@ -136,15 +136,8 @@ describe("client.rollback", () => { foo: "bar", }); const marker = client.getMarkerFromId("markerId") as Marker; - client.annotateMarker( - marker, - { [reservedMarkerIdKey]: "markerId", abc: "def" }, - { name: "rewrite" }, - ); - client.rollback?.( - { type: MergeTreeDeltaType.ANNOTATE, combiningOp: { name: "rewrite" } }, - client.peekPendingSegmentGroups(), - ); + client.annotateMarker(marker, { [reservedMarkerIdKey]: "markerId", abc: "def" }); + client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); const properties = marker.getProperties(); assert.equal(properties?.foo, "bar"); @@ -156,15 +149,8 @@ describe("client.rollback", () => { foo: "bar", }); const marker = client.getMarkerFromId("markerId") as Marker; - client.annotateMarker( - marker, - { [reservedMarkerIdKey]: "markerId", abc: "def", foo: null }, - { name: "rewrite" }, - ); - client.rollback?.( - { type: MergeTreeDeltaType.ANNOTATE, combiningOp: { name: "rewrite" } }, - client.peekPendingSegmentGroups(), - ); + client.annotateMarker(marker, { [reservedMarkerIdKey]: "markerId", abc: "def", foo: null }); + client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); const properties = marker.getProperties(); assert.equal(properties?.foo, "bar"); @@ -172,7 +158,7 @@ describe("client.rollback", () => { }); it("Should rollback annotate causes split string", () => { client.insertTextLocal(0, "abcdefg"); - client.annotateRangeLocal(1, 3, { foo: "bar" }, undefined); + client.annotateRangeLocal(1, 3, { foo: "bar" }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); for (let i = 0; i < 4; i++) { @@ -183,7 +169,7 @@ describe("client.rollback", () => { it("Should rollback annotate over split string", () => { client.insertTextLocal(0, "abfg"); client.insertTextLocal(1, "cde"); - client.annotateRangeLocal(1, 6, { foo: "bar" }, undefined); + client.annotateRangeLocal(1, 6, { foo: "bar" }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); for (let i = 0; i < 7; i++) { @@ -193,7 +179,7 @@ describe("client.rollback", () => { }); it("Should rollback annotate that later gets split", () => { client.insertTextLocal(0, "abfg"); - client.annotateRangeLocal(0, 4, { foo: "bar" }, undefined); + client.annotateRangeLocal(0, 4, { foo: "bar" }); client.insertTextLocal(1, "cde"); client.rollback?.({ type: MergeTreeDeltaType.INSERT }, client.peekPendingSegmentGroups()); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); @@ -206,9 +192,9 @@ describe("client.rollback", () => { }); it("Should rollback annotates with multiple previous property sets", () => { client.insertTextLocal(0, "acde"); - client.annotateRangeLocal(0, 3, { foo: "one" }, undefined); - client.annotateRangeLocal(2, 4, { foo: "two" }, undefined); - client.annotateRangeLocal(0, 3, { foo: "three" }, undefined); + client.annotateRangeLocal(0, 3, { foo: "one" }); + client.annotateRangeLocal(2, 4, { foo: "two" }); + client.annotateRangeLocal(0, 3, { foo: "three" }); client.insertTextLocal(1, "b"); client.rollback?.({ type: MergeTreeDeltaType.INSERT }, client.peekPendingSegmentGroups()); @@ -246,8 +232,8 @@ describe("client.rollback", () => { }); it("Should rollback annotate with same prop", () => { client.insertTextLocal(0, "abcde"); - client.annotateRangeLocal(2, 3, { foo: "bar" }, undefined); - client.annotateRangeLocal(1, 4, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 3, { foo: "bar" }); + client.annotateRangeLocal(1, 4, { foo: "bar" }); client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, client.peekPendingSegmentGroups()); for (let i = 0; i < 5; i++) { @@ -269,7 +255,7 @@ describe("client.rollback", () => { client.getCurrentSeq(), ), ); - client.annotateRangeLocal(2, 3, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 3, { foo: "bar" }); const segmentGroup = client.peekPendingSegmentGroups() as SegmentGroup; const segment: IMergeLeaf = segmentGroup.segments[0]; client.rollback?.({ type: MergeTreeDeltaType.ANNOTATE }, segmentGroup); @@ -305,7 +291,7 @@ describe("client.rollback", () => { }); it("Should rollback delete across split segments", () => { client.insertTextLocal(0, "abcde"); - client.annotateRangeLocal(2, 3, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 3, { foo: "bar" }); client.removeRangeLocal(1, 4); client.rollback?.({ type: MergeTreeDeltaType.REMOVE }, client.peekPendingSegmentGroups()); @@ -403,7 +389,7 @@ describe("client.rollback", () => { it("Should rollback multiple overlapping edits", () => { client.insertTextLocal(0, "abcdefg"); client.insertTextLocal(3, "123"); - client.annotateRangeLocal(2, 5, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 5, { foo: "bar" }); client.removeRangeLocal(3, 7); client.rollback?.({ type: MergeTreeDeltaType.REMOVE }, client.peekPendingSegmentGroups()); @@ -457,7 +443,7 @@ describe("client.rollback", () => { client.rollback?.({ type: MergeTreeDeltaType.REMOVE }, client.peekPendingSegmentGroups()); assert.equal(client.getText(), "abc123defg"); - client.annotateRangeLocal(2, 8, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 8, { foo: "bar" }); for (let i = 0; i < client.getText().length; i++) { const props = client.getPropertiesAtPosition(i); if (i >= 2 && i < 8) { @@ -479,8 +465,8 @@ describe("client.rollback", () => { }); it("Should rollback overlapping annotates and remove", () => { client.insertTextLocal(0, "abc123defg"); - client.annotateRangeLocal(0, 6, { foo: "one" }, undefined); - client.annotateRangeLocal(5, 10, { foo: "two" }, undefined); + client.annotateRangeLocal(0, 6, { foo: "one" }); + client.annotateRangeLocal(5, 10, { foo: "two" }); client.removeRangeLocal(4, 8); client.rollback?.({ type: MergeTreeDeltaType.REMOVE }, client.peekPendingSegmentGroups()); assert.equal(client.getPropertiesAtPosition(4)?.foo, "one"); @@ -511,7 +497,7 @@ describe("client.rollback", () => { client.removeRangeLocal(2, 4); assert.equal(client.getText(), "abefg"); - client.annotateRangeLocal(2, 5, { foo: "bar" }, undefined); + client.annotateRangeLocal(2, 5, { foo: "bar" }); for (let i = 0; i < client.getText().length; i++) { const props = client.getPropertiesAtPosition(i); if (i >= 2 && i < 5) { @@ -547,7 +533,7 @@ describe("client.rollback", () => { logger.validate({ baseText: "1245" }); msg = remoteClient.makeOpMessage( - remoteClient.annotateRangeLocal(0, 3, { foo: "bar" }, undefined), + remoteClient.annotateRangeLocal(0, 3, { foo: "bar" }), ++seq, ); clients.forEach((c) => { diff --git a/packages/dds/merge-tree/src/test/client.rollbackFarm.spec.ts b/packages/dds/merge-tree/src/test/client.rollbackFarm.spec.ts index 45a4501eb724..1dd4f7d0b62d 100644 --- a/packages/dds/merge-tree/src/test/client.rollbackFarm.spec.ts +++ b/packages/dds/merge-tree/src/test/client.rollbackFarm.spec.ts @@ -68,9 +68,9 @@ describe("MergeTree.Client", () => { ); while (rollbackMsgs.length > 0) { const msg = rollbackMsgs.pop(); - // TODO: The type here is probably MergeTreeDeltaType but omitting GROUP, given the typing of the rollback method. - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - clients[msg![0].clientId as string].rollback?.( + // TODO: The type here is probably MergeTreeDeltaType but + // omitting GROUP, given the typing of the rollback method. + clients[msg![0].clientId!].rollback?.( { type: (msg![0].contents as { type?: unknown }).type }, msg![1], ); diff --git a/packages/dds/merge-tree/src/test/client.spec.ts b/packages/dds/merge-tree/src/test/client.spec.ts index f5ccd40b8e02..2bf66ed27349 100644 --- a/packages/dds/merge-tree/src/test/client.spec.ts +++ b/packages/dds/merge-tree/src/test/client.spec.ts @@ -215,7 +215,7 @@ describe("TestClient", () => { assert(insertOp); const markerInfo = client.getContainingSegment(0); const marker = markerInfo.segment as Marker; - const annotateOp = client.annotateMarker(marker, { foo: "bar" }, undefined); + const annotateOp = client.annotateMarker(marker, { foo: "bar" }); assert(annotateOp); assert(marker.properties); assert(marker.properties.foo, "bar"); diff --git a/packages/dds/merge-tree/src/test/collections.list.spec.ts b/packages/dds/merge-tree/src/test/collections.list.spec.ts index 1ca71303e142..bba0daefac4c 100644 --- a/packages/dds/merge-tree/src/test/collections.list.spec.ts +++ b/packages/dds/merge-tree/src/test/collections.list.spec.ts @@ -4,14 +4,14 @@ */ import { strict as assert } from "assert"; -import { List, walkList } from "../collections"; +import { DoublyLinkedList, walkList } from "../collections"; -describe("Collections.List", () => { +describe("Collections.DoublyLinkedList", () => { const listCount = 5; - let list: List; + let list: DoublyLinkedList; beforeEach(() => { - list = new List(); + list = new DoublyLinkedList(); for (let i = 0; i < listCount; i++) { list.unshift(i); } @@ -90,7 +90,8 @@ describe("Collections.List", () => { const spliceNode = nodesArray[spliceIndex]; const arraySplice = nodesArray.splice(spliceIndex); - const listSplice = spliceNode === undefined ? new List() : list.splice(spliceNode); + const listSplice = + spliceNode === undefined ? new DoublyLinkedList() : list.splice(spliceNode); assert.equal(list.length, nodesArray.length, "remaining lengths don't match"); assert.equal(listSplice.length, arraySplice.length, "spliced lengths don't match"); diff --git a/packages/dds/merge-tree/src/test/index.ts b/packages/dds/merge-tree/src/test/index.ts index 0ee7d38bb3c2..1c0e2b179c42 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -38,8 +38,7 @@ export { runMergeTreeOperationRunner, TestOperation, } from "./mergeTreeOperationRunner"; -export { ProxString, TST, TSTNode, TSTResult } from "./tst"; -export { ClientSeq, clientSeqComparer, LRUSegment, MergeTree } from "../mergeTree"; +export { LRUSegment, MergeTree } from "../mergeTree"; export { MergeTreeTextHelper } from "../MergeTreeTextHelper"; export { SnapshotLegacy } from "../snapshotlegacy"; export { @@ -47,14 +46,9 @@ export { appendToMergeTreeDeltaRevertibles, BaseSegment, Client, - clone, CollaborationWindow, - combine, - compareNumbers, compareReferencePositions, - compareStrings, ConflictAction, - createAnnotateMarkerOp, createAnnotateRangeOp, createDetachedLocalReferencePosition, createGroupOp, @@ -64,19 +58,10 @@ export { createRemoveRangeOp, debugMarkerToString, DetachedReferencePosition, - Dictionary, discardMergeTreeDeltaRevertible, - extend, - extendIfUndefined, - ICombiningOp, - IConsensusInfo, - IConsensusValue, - IIntegerRange, IJSONMarkerSegment, IJSONSegment, - IJSONTextSegment, IMarkerDef, - IMarkerModifiedAction, IMergeNodeCommon, IMergeTreeAnnotateMsg, IMergeTreeClientSequenceArgs, @@ -91,7 +76,6 @@ export { IMergeTreeRemoveMsg, IMergeTreeSegmentDelta, IMergeTreeTextHelper, - internedSpaces, IRBAugmentation, IRBMatcher, IRelativePosition, @@ -99,7 +83,6 @@ export { ISegment, ISegmentAction, KeyComparer, - LocalClientId, LocalReferenceCollection, LocalReferencePosition, MapLike, @@ -107,55 +90,40 @@ export { matchProperties, maxReferencePosition, MergeNode, - MergeTreeDeltaCallback, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, MergeTreeDeltaRevertible, MergeTreeDeltaType, - MergeTreeMaintenanceCallback, MergeTreeMaintenanceType, MergeTreeRevertibleDriver, minReferencePosition, - NonCollabClient, PropertiesManager, - PropertiesRollback, Property, PropertyAction, PropertySet, - QProperty, - RangeStackMap, - RBColor, RBNode, RBNodeActions, RedBlackTree, ReferencePosition, ReferenceType, - refGetRangeLabels, refGetTileLabels, - refHasRangeLabel, - refHasRangeLabels, refHasTileLabel, refHasTileLabels, refTypeIncludesFlag, reservedMarkerIdKey, reservedMarkerSimpleTypeKey, - reservedRangeLabelsKey, reservedTileLabelsKey, revertMergeTreeDeltaRevertibles, - SegmentAccumulator, SegmentGroup, SegmentGroupCollection, - SortedDictionary, SortedSegmentSet, SortedSegmentSetItem, SortedSet, - Stack, TextSegment, toRemovalInfo, Trackable, TrackingGroup, TrackingGroupCollection, - TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, UniversalSequenceNumber, } from ".."; diff --git a/packages/dds/merge-tree/src/test/mergeTree.annotate.deltaCallback.spec.ts b/packages/dds/merge-tree/src/test/mergeTree.annotate.deltaCallback.spec.ts index b49722b9e11f..c80c0d8f8b32 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.annotate.deltaCallback.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.annotate.deltaCallback.spec.ts @@ -45,7 +45,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -66,7 +65,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, currentSequenceNumber, localClientId, ++currentSequenceNumber, @@ -98,7 +96,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -134,7 +131,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -170,7 +166,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -206,7 +201,6 @@ describe("MergeTree", () => { { foo: "bar", }, - undefined, remoteSequenceNumber, remoteClientId, ++remoteSequenceNumber, 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 59bd36cced4d..bed4c4c15d19 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts @@ -3,13 +3,11 @@ * Licensed under the MIT License. */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ - import { strict as assert } from "assert"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { LocalClientId, UnassignedSequenceNumber, UniversalSequenceNumber } from "../constants"; import { BaseSegment, Marker } from "../mergeTreeNodes"; -import { ICombiningOp, MergeTreeDeltaType, ReferenceType } from "../ops"; +import { MergeTreeDeltaType, ReferenceType } from "../ops"; import { TextSegment } from "../textSegment"; import { MergeTree } from "../mergeTree"; import { insertSegments } from "./testUtils"; @@ -58,7 +56,6 @@ describe("MergeTree", () => { { propertySource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, currentSequenceNumber + 1, @@ -81,7 +78,6 @@ describe("MergeTree", () => { { propertySource: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -114,7 +110,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -139,7 +134,6 @@ describe("MergeTree", () => { { secondProperty: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -176,7 +170,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, secondChangeProps, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -191,7 +184,6 @@ describe("MergeTree", () => { splitPos, annotateEnd, splitOnlyProps, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -231,7 +223,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 1); @@ -253,7 +245,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 0); @@ -275,7 +267,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 0); @@ -297,7 +289,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -326,7 +317,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); const segmentInfo = mergeTree.getContainingSegment( @@ -349,7 +340,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -359,7 +350,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -396,7 +386,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props2, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -413,7 +402,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props3, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -433,7 +421,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -449,7 +437,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -465,7 +453,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -480,7 +468,6 @@ describe("MergeTree", () => { { secondSource: "local2", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -496,7 +483,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -507,7 +494,6 @@ describe("MergeTree", () => { remoteOnly: 1, secondSource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -535,7 +521,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -580,7 +565,6 @@ describe("MergeTree", () => { { propertySource: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -613,7 +597,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -631,7 +614,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert(segmentInfo.segment?.segmentGroups.empty); @@ -643,15 +626,11 @@ describe("MergeTree", () => { const props = { propertySource: "local", }; - const combiningOp: ICombiningOp = { - name: "rewrite", - }; beforeEach(() => { mergeTree.annotateRange( annotateStart, annotateEnd, props, - combiningOp, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -667,7 +646,6 @@ describe("MergeTree", () => { propertySource: "local2", secondProperty: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -692,7 +670,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -708,13 +685,12 @@ describe("MergeTree", () => { assert.equal(segment.segmentGroups.size, 1); assert.equal(segment.properties?.propertySource, "local"); - assert(!segment.properties?.remoteProperty); + assert.equal(segment.properties?.remoteProperty, 1); }); it("sequenced local before remote", () => { mergeTree.ackPendingSegment({ op: { - combiningOp, pos1: annotateStart, pos2: annotateEnd, props, @@ -722,7 +698,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -732,7 +708,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -758,7 +733,6 @@ describe("MergeTree", () => { { secondSource: "local2", }, - combiningOp, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -767,7 +741,6 @@ describe("MergeTree", () => { mergeTree.ackPendingSegment({ op: { - combiningOp, pos1: annotateStart, pos2: annotateEnd, props, @@ -775,7 +748,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -786,7 +759,6 @@ describe("MergeTree", () => { remoteOnly: 1, secondSource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -800,8 +772,8 @@ describe("MergeTree", () => { ); const segment = segmentInfo.segment as BaseSegment; - assert(!segment.properties?.remoteOnly); - assert(!segment.properties?.propertySource); + assert.equal(segment.properties?.remoteOnly, 1); + assert.equal(segment.properties?.propertySource, "remote"); assert.equal(segment.properties?.secondSource, "local2"); }); }); diff --git a/packages/dds/merge-tree/src/test/mergeTree.markRangeRemoved.deltaCallback.spec.ts b/packages/dds/merge-tree/src/test/mergeTree.markRangeRemoved.deltaCallback.spec.ts index 88a54b1d11d0..0b9e2fde92b0 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.markRangeRemoved.deltaCallback.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.markRangeRemoved.deltaCallback.spec.ts @@ -82,10 +82,9 @@ describe("MergeTree", () => { pos2: end, type: MergeTreeDeltaType.REMOVE, }, - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); // Move currentSeq/minSeq past the seq# at which the removal was ACKed. diff --git a/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts index a04fa2b3c091..f8cd270e3268 100644 --- a/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts +++ b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts @@ -26,8 +26,14 @@ export type TestOperation = ( export const removeRange: TestOperation = (client: TestClient, opStart: number, opEnd: number) => client.removeRangeLocal(opStart, opEnd); +export const obliterateRange: TestOperation = ( + client: TestClient, + opStart: number, + opEnd: number, +) => client.obliterateRangeLocal(opStart, opEnd); + export const annotateRange: TestOperation = (client: TestClient, opStart: number, opEnd: number) => - client.annotateRangeLocal(opStart, opEnd, { client: client.longClientId }, undefined); + client.annotateRangeLocal(opStart, opEnd, { client: client.longClientId }); export const insertAtRefPos: TestOperation = ( client: TestClient, diff --git a/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts b/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts new file mode 100644 index 000000000000..43ace9a16b2c --- /dev/null +++ b/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts @@ -0,0 +1,1824 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "assert"; +import { LoggingError } from "@fluidframework/telemetry-utils"; +import { PartialSequenceLengths, verify, verifyExpected } from "../partialLengths"; +import { MergeTree } from "../mergeTree"; +import { ReconnectTestHelper } from "./reconnectHelper"; + +/** + * Some tests contain ASCII diagrams of the trees to make it easier to reason about + * structure. The format of these diagrams is this: + * - segments are separated by `-`. + * - removed ranges are surrounded by `[` ... `]` + * - obliterated ranges are surrounded by `(` ... `)` + * + * In cases where these ranges are ambiguous, there are additional diagrams + * containing `v` and `-`, with the `v` denoting the start and end of the range + * + * E.g. + * + * ``` + * v---v + * [ABC] + * ``` + * + * This diagram describes a single segment, `ABC`, that has been removed. + * + * + * ``` + * v-----v + * v---v + * [[ABC]] + * ``` + * + * This diagram describes a single segment, `ABC`, that has been concurrently + * removed by two clients. + */ + +for (const incremental of [true, false]) { + describe(`obliterate partial lengths incremental = ${incremental}`, () => { + beforeEach(() => { + PartialSequenceLengths.options.verifier = verify; + PartialSequenceLengths.options.verifyExpected = verifyExpected; + MergeTree.options.incrementalUpdate = incremental; + }); + + afterEach(() => { + PartialSequenceLengths.options.verifier = undefined; + PartialSequenceLengths.options.verifyExpected = undefined; + MergeTree.options.incrementalUpdate = true; + }); + + it("length of children does not differ from parent when overlapping remove+obliterate", () => { + const helper = new ReconnectTestHelper(); + + // ABCDEFGH + // I-[J]-(KLM-[ABC]-D-123456-E-[FG]-H) + + helper.insertText("A", 0, "ABCDEFGH"); + helper.processAllOps(); + helper.removeRange("C", 0, 3); + helper.insertText("C", 1, "123456"); + helper.removeRange("A", 5, 7); + helper.insertText("A", 0, "IJKLM"); + helper.obliterateRange("A", 2, 11); + helper.removeRange("A", 1, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "I"); + + helper.logger.validate(); + }); + + it("deletes concurrent insert that occurs after obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 4); + helper.insertText("C", 2, "X"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("deletes concurrent insert that occurs before obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.insertText("C", 2, "X"); + helper.obliterateRange("B", 0, 4); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("does not delete unacked segment at start of string", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "ABC"); + helper.obliterateRange("C", 2, 3); + helper.insertText("B", 0, "X"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "XAB"); + assert.equal(helper.clients.B.getText(), "XAB"); + assert.equal(helper.clients.C.getText(), "XAB"); + + helper.logger.validate(); + }); + + it("throws when local obliterate has range end outside length of local string", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "A"); + helper.insertText("C", 0, "B"); + + try { + helper.obliterateRange("C", 0, 2); + assert.fail("should not be possible to obliterate outside local range"); + } catch (e) { + assert(e instanceof LoggingError); + assert.equal(e.message, "RangeOutOfBounds"); + } + }); + + it("does not delete when obliterate immediately after insert", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "A"); + helper.obliterateRange("C", 0, 1); + helper.insertText("B", 0, "W"); + helper.insertText("C", 0, "D"); + helper.obliterateRange("C", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "W"); + assert.equal(helper.clients.B.getText(), "W"); + assert.equal(helper.clients.C.getText(), "W"); + + helper.logger.validate(); + }); + + it("does not delete remote insert when between local insert+obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "X"); + helper.obliterateRange("C", 0, 1); + helper.insertText("C", 0, "B"); + helper.obliterateRange("C", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "X"); + assert.equal(helper.clients.B.getText(), "X"); + assert.equal(helper.clients.C.getText(), "X"); + + helper.logger.validate(); + }); + + it("does not delete remote insert when between local insert+obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "A"); + helper.obliterateRange("C", 0, 1); + helper.insertText("B", 0, "B"); + helper.insertText("C", 0, "X"); + helper.obliterateRange("B", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "X"); + assert.equal(helper.clients.B.getText(), "X"); + assert.equal(helper.clients.C.getText(), "X"); + + helper.logger.validate(); + }); + + it("does not delete remote insert when in middle of segment", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "ABC"); + helper.obliterateRange("C", 2, 3); + helper.obliterateRange("C", 0, 1); + helper.insertText("B", 0, "X"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "XB"); + assert.equal(helper.clients.B.getText(), "XB"); + assert.equal(helper.clients.C.getText(), "XB"); + + helper.logger.validate(); + }); + + it("deletes segment inserted into locally obliterated segment", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "X"); + helper.insertText("C", 0, "B"); + helper.obliterateRange("C", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("updates lengths after obliterated insertion", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "X"); + helper.insertText("C", 0, "N"); + helper.obliterateRange("C", 0, 2); + helper.insertText("B", 1, "B"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + assert.equal(helper.clients.A.getLength(), 0); + assert.equal(helper.clients.B.getLength(), 0); + assert.equal(helper.clients.C.getLength(), 0); + + helper.logger.validate(); + }); + + it("updates lengths when insertion causes tree to split", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "0"); + helper.insertText("C", 0, "123"); + helper.insertText("B", 0, "BB"); + helper.insertText("C", 0, "GGG"); + helper.obliterateRange("C", 2, 5); + helper.insertText("B", 1, "A"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText().length, helper.clients.A.getLength()); + assert.equal(helper.clients.B.getText().length, helper.clients.B.getLength()); + assert.equal(helper.clients.C.getText().length, helper.clients.C.getLength()); + + assert.equal(helper.clients.A.getText(), "GG30"); + + helper.logger.validate(); + }); + + it("length of node split by insertion does not count remotely obliterated segments", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "1"); + helper.insertText("A", 0, "2"); + helper.insertText("C", 0, "XXXX"); + helper.insertText("B", 0, "ABC"); + helper.insertText("C", 0, "GGG"); + helper.obliterateRange("C", 2, 6); + helper.insertText("C", 1, "D"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "GDGX21"); + assert.equal(helper.clients.C.getText(), "GDGX21"); + + helper.logger.validate(); + }); + + it("length of node split by obliterate does not count remotely obliterated segments", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "1"); + helper.insertText("A", 0, "2"); + helper.insertText("C", 0, "XXXX"); + helper.insertText("B", 0, "A"); + helper.insertText("C", 0, "GGG"); + helper.obliterateRange("C", 2, 6); + helper.insertText("C", 1, "C"); + helper.insertText("B", 1, "D"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "GCGX21"); + assert.equal(helper.clients.B.getText(), "GCGX21"); + + helper.logger.validate(); + }); + + it("counts remotely but not concurrently inserted segments for length when tree is split", () => { + const helper = new ReconnectTestHelper(); + + // a-b-c-d-e-123 + // (a-b)-c-d-e-1-[2]-3 + + helper.insertText("B", 0, "123"); + helper.insertText("C", 0, "e"); + helper.insertText("C", 0, "d"); + helper.insertText("C", 0, "c"); + helper.insertText("C", 0, "b"); + helper.insertText("C", 0, "a"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 2); + helper.removeRange("B", 4, 5); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "cde13"); + assert.equal(helper.clients.C.getText(), "cde13"); + + helper.logger.validate(); + }); + + it("does obliterate X for all clients", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "DE"); + helper.obliterateRange("B", 0, 1); + helper.insertText("A", 0, "X"); + helper.insertText("B", 0, "ABC"); + helper.obliterateRange("B", 2, 4); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "AB"); + assert.equal(helper.clients.C.getText(), "AB"); + + helper.logger.validate(); + }); + + it("does not include remote but unacked segments in partial len calculation", () => { + const helper = new ReconnectTestHelper(); + + // 89-4567-123-X + // 8-(9-4-w-567-1)-23-Y-X + + helper.insertText("A", 0, "X"); + helper.insertText("C", 0, "123"); + helper.insertText("C", 0, "4567"); + helper.insertText("B", 0, "89"); + helper.processAllOps(); + helper.obliterateRange("C", 1, 7); + helper.insertText("A", 3, "w"); + helper.insertText("C", 3, "Y"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "823YX"); + assert.equal(helper.clients.B.getText(), "823YX"); + + helper.logger.validate(); + }); + + it("correctly accounts for overlapping obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.obliterateRange("C", 0, 1); + helper.obliterateRange("B", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "B"); + assert.equal(helper.clients.B.getText(), "B"); + assert.equal(helper.clients.C.getText(), "B"); + + helper.logger.validate(); + }); + + it("correctly accounts for overlapping obliterate and remove", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.removeRange("C", 0, 1); + helper.obliterateRange("B", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "B"); + assert.equal(helper.clients.B.getText(), "B"); + assert.equal(helper.clients.C.getText(), "B"); + + helper.logger.validate(); + }); + + it("clones movedClientIds array during insert", () => { + const helper = new ReconnectTestHelper(); + + // the bug found here: + // the X was skipped over by client `A` because it had already been + // deleted, so its length at refSeq was 0 + // + // this was due to the movedClientIds array not being properly cloned + // when marking obliterated during insert + + helper.insertText("C", 0, "ABCD"); + helper.processAllOps(); + helper.insertText("B", 2, "X"); + helper.obliterateRange("A", 1, 3); + helper.obliterateRange("B", 1, 4); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "AD"); + assert.equal(helper.clients.B.getText(), "AD"); + assert.equal(helper.clients.C.getText(), "AD"); + + helper.logger.validate(); + }); + + it("client partial lens consider overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "123"); + helper.insertText("A", 0, "ABCDEF"); + helper.processAllOps(); + helper.obliterateRange("B", 2, 3); + helper.obliterateRange("C", 1, 4); + helper.obliterateRange("C", 4, 5); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "AEF13"); + assert.equal(helper.clients.B.getText(), "AEF13"); + assert.equal(helper.clients.C.getText(), "AEF13"); + + helper.logger.validate(); + }); + + it("client partial lens consider overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("C", 0, "X"); + helper.insertText("C", 0, "ABCDEFG"); + helper.processAllOps(); + helper.obliterateRange("B", 2, 3); + helper.obliterateRange("C", 1, 4); + helper.obliterateRange("C", 2, 3); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "AEGX"); + assert.equal(helper.clients.C.getText(), "AEGX"); + + helper.logger.validate(); + }); + + it("tracks obliterate refSeq when acking op for partial len calculation", () => { + const helper = new ReconnectTestHelper(); + + // v-----------------------v + // v--v + // v--v + // 6-(345-AB-(CD)-E-(FG)-HI-1)-2 + + helper.insertText("A", 0, "12"); + helper.insertText("B", 0, "ABCDEFGHI"); + helper.insertText("A", 0, "345"); + helper.obliterateRange("A", 0, 4); + helper.obliterateRange("B", 2, 4); + helper.insertText("A", 0, "6"); + helper.obliterateRange("B", 3, 5); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "62"); + assert.equal(helper.clients.B.getText(), "62"); + + helper.logger.validate(); + }); + + it("does not have negative len when segment obliterated before insert", () => { + const helper = new ReconnectTestHelper(); + + // 1234567-D-C-AB + // 12-([3-X-45]-67)-D-C-AB + + helper.insertText("A", 0, "AB"); + helper.insertText("A", 0, "C"); + helper.insertText("A", 0, "D"); + helper.insertText("A", 0, "1234567"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 2, 7); + helper.removeRange("A", 2, 5); + helper.insertText("C", 3, "X"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "12B"); + assert.equal(helper.clients.B.getText(), "12B"); + assert.equal(helper.clients.C.getText(), "12B"); + + helper.logger.validate(); + }); + + it("does not have negative len when segment obliterated before insert", () => { + const helper = new ReconnectTestHelper(); + + // ABCDE-1-[2]-3 + // (A-XX-B)-(CD)-E-1-3 + + helper.insertText("B", 0, "123"); + helper.insertText("C", 0, "ABCDE"); + helper.removeRange("B", 1, 2); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 0, 2); + helper.obliterateRange("C", 0, 2); + helper.insertText("B", 1, "XX"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "E13"); + assert.equal(helper.clients.B.getText(), "E13"); + assert.equal(helper.clients.C.getText(), "E13"); + + helper.logger.validate(); + }); + + it("deletes segments between two obliterates with different seq", () => { + const helper = new ReconnectTestHelper(); + + // 90-8-1234-(5)-67-D-C-B-A + // 9-(EFG-[0-8-1234-(5)-67)]-D-C-B-A + + helper.insertText("A", 0, "A"); + helper.insertText("C", 0, "B"); + helper.insertText("C", 0, "C"); + helper.insertText("C", 0, "D"); + helper.insertText("B", 0, "1234567"); + helper.obliterateRange("B", 4, 5); + helper.insertText("A", 0, "8"); + helper.insertText("A", 0, "90"); + helper.processAllOps(); + helper.removeRange("C", 1, 9); + helper.insertText("A", 1, "EFG"); + helper.obliterateRange("A", 1, 11); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "9DCBA"); + + helper.logger.validate(); + }); + + it("deletes inserted segment when obliterate of different seq in-between", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "AB"); + helper.insertText("B", 0, "E"); + helper.obliterateRange("A", 0, 1); + helper.insertText("A", 1, "12"); + helper.insertText("A", 0, "CD"); + helper.obliterateRange("A", 1, 4); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "C2"); + assert.equal(helper.clients.B.getText(), "C2"); + assert.equal(helper.clients.C.getText(), "C2"); + + helper.logger.validate(); + }); + + it("deletes inserted segment when obliterate of different seq in-between", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "ABC"); + helper.obliterateRange("A", 1, 2); + helper.processAllOps(); + helper.insertText("A", 1, "D"); + helper.obliterateRange("C", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("deletes inserted segment when obliterate of different seq in-between", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "ABC"); + helper.obliterateRange("A", 1, 2); + helper.processAllOps(); + helper.insertText("A", 1, "D"); + helper.obliterateRange("C", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("considers obliterated local segments as remotely obliterate", () => { + const helper = new ReconnectTestHelper(); + + // G-(H-F-I-C)-J-DE-A-(B) + // G-J-(H-F-I-CD)-E + + helper.insertText("A", 0, "AB"); + helper.obliterateRange("A", 1, 2); + helper.insertText("C", 0, "CDE"); + helper.insertText("B", 0, "F"); + helper.insertText("C", 0, "GH"); + helper.obliterateRange("C", 1, 3); + helper.insertText("B", 1, "I"); + helper.insertText("C", 1, "J"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "GJDEA"); + assert.equal(helper.clients.B.getText(), "GJDEA"); + + helper.logger.validate(); + }); + + it("traverses hier block in obliterated when len at ref seq is >0 and len at len seq == 0", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "AB"); + helper.insertText("A", 2, "CD"); + helper.removeRange("A", 1, 3); + helper.insertText("C", 0, "12345"); + helper.insertText("B", 0, "EFG"); + helper.insertText("B", 1, "HIJKL"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 6, 12); + helper.removeRange("A", 5, 7); + helper.obliterateRange("C", 7, 9); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), helper.clients.D.getText()); + assert.equal(helper.clients.B.getText(), "EHIJKAD"); + + helper.logger.validate(); + }); + + it("traverses hier block in obliterate when len at ref seq is >0 and len at len seq == 0", () => { + const helper = new ReconnectTestHelper(); + + // [E]-FGH-12-[A]-[B]-CD + // 3-4-F-[G-(H-1)-2]-CD + + helper.insertText("B", 0, "ABCD"); + helper.removeRange("B", 0, 1); + helper.insertText("C", 0, "12"); + helper.insertText("A", 0, "EFGH"); + helper.removeRange("B", 1, 2); + helper.removeRange("A", 0, 1); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("A", 1, 5); + helper.obliterateRange("B", 2, 4); + helper.insertText("A", 0, "3"); + helper.insertText("A", 0, "4"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "43FBD"); + assert.equal(helper.clients.B.getText(), "43FBD"); + + helper.logger.validate(); + }); + + it("ignores segments where movedSeq < seq for partial len calculations", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "ABC"); + helper.insertText("A", 0, "DEF"); + helper.removeRange("A", 1, 2); + helper.insertText("B", 0, "123456"); + helper.obliterateRange("B", 2, 7); + helper.insertText("A", 1, "Y"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("B", 4, "X"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "12BCX"); + assert.equal(helper.clients.B.getText(), "12BCX"); + assert.equal(helper.clients.C.getText(), "12BCX"); + + helper.logger.validate(); + }); + + it("accounts for overlapping obliterates from same client", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 1); + helper.obliterateRange("B", 0, 1); + helper.removeRange("A", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("accounts for concurrently obliterated segments from the perspective of the inserting client for partial lengths", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "A"); + helper.insertText("C", 0, "B"); + helper.insertText("C", 0, "C"); + helper.insertText("A", 0, "1234"); + helper.processAllOps(); + helper.obliterateRange("C", 1, 3); + helper.insertText("A", 2, "D"); + helper.insertText("A", 4, "E"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "1E4CBA"); + assert.equal(helper.clients.B.getText(), "1E4CBA"); + + helper.logger.validate(); + }); + + it("traverses segments when there is a local obliterate", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("A", 0, "AB"); + helper.obliterateRange("A", 0, 1); + helper.insertText("C", 0, "12"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("C", 2, "C"); + helper.obliterateRange("A", 0, 3); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("keeps track of all obliterates on a segment", () => { + const helper = new ReconnectTestHelper(); + + // B-A + // (B-C-(A)) + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "B"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 1, 2); + // bug here: because segment A has already been obliterated, we wouldn't + // mark it obliterated by this op as well, meaning that segments in + // this range would look to the right and not find a matching move seq + helper.obliterateRange("A", 0, 2); + helper.insertText("B", 1, "C"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + assert.equal(helper.clients.D.getText(), ""); + + helper.logger.validate(); + }); + + it("many overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + // EF-ABCD + // (1)-2-((E)-F-A)-B-(C)-D + + helper.insertText("C", 0, "ABCD"); + helper.insertText("B", 0, "EF"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 3); + helper.insertText("A", 0, "12"); + helper.removeRange("C", 0, 1); + helper.obliterateRange("A", 0, 1); + helper.obliterateRange("B", 1, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "2BD"); + + helper.logger.validate(); + }); + + it("overlapping obliterates at start", () => { + const helper = new ReconnectTestHelper(); + + // 12345-B-A + // ((1-C-2)-3)-4-D-5-B-A + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("A", 0, "12345"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 0, 2); + helper.insertText("C", 1, "C"); + helper.obliterateRange("C", 0, 4); + helper.insertText("C", 1, "D"); + helper.processAllOps(); + helper.logger.validate(); + }); + + it("partial lengths updated when local insert is acked", () => { + const helper = new ReconnectTestHelper(); + + // A-BCDEF + // (A-B-G-C)-D-I-E-H-F + + helper.insertText("A", 0, "A"); + helper.insertText("A", 1, "BCDEF"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 0, 3); + helper.insertText("A", 2, "G"); + helper.insertText("B", 5, "H"); + helper.insertText("C", 1, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "DIEHF"); + assert.equal(helper.clients.B.getText(), "DIEHF"); + assert.equal(helper.clients.C.getText(), "DIEHF"); + assert.equal(helper.clients.D.getText(), "DIEHF"); + + helper.logger.validate(); + }); + + it("two local obliterates get different seq numbers after ack", () => { + const helper = new ReconnectTestHelper(); + + // C-AB + // (C-A)-D-(B) + + helper.insertText("C", 0, "AB"); + helper.insertText("A", 0, "C"); + helper.processAllOps(); + helper.logger.validate(); + // bug here: when the op is acked by client C, it would incorrectly give + // segment B the same movedSeq despite coming from a different op + helper.obliterateRange("C", 0, 2); + helper.insertText("B", 2, "D"); + helper.obliterateRange("C", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "D"); + assert.equal(helper.clients.C.getText(), "D"); + + helper.logger.validate(); + }); + + it("acks remote segment obliterated by local op", () => { + const helper = new ReconnectTestHelper(); + + // (D-C-A)-B + // (D-C-A)-B-E + + helper.insertText("B", 0, "AB"); + helper.insertText("A", 0, "C"); + helper.insertText("B", 0, "D"); + // bug here: when the op is acked by client B, it wouldn't correctly + // visit the segment "C", leaving the obliterate unacked + helper.obliterateRange("B", 0, 2); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("C", 1, "E"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "BE"); + assert.equal(helper.clients.B.getText(), "BE"); + + helper.logger.validate(); + }); + + it("skips segments obliterated before refSeq when traversing for insertion", () => { + const helper = new ReconnectTestHelper(); + + // CDE-(A)-B + // C-(DE-F-(A)-B) + + helper.insertText("A", 0, "AB"); + helper.obliterateRange("A", 0, 1); + helper.insertText("A", 0, "CDE"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 1, 4); + // bug here: when traversing to see if segment should be obliterated after + // insertion, traversal would stop at segment A because it was obliterated + // before the refSeq, which made it appear as an un-deleted segment + helper.insertText("B", 3, "F"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "C"); + assert.equal(helper.clients.B.getText(), "C"); + assert.equal(helper.clients.C.getText(), "C"); + + helper.logger.validate(); + }); + + it("applies correct movedSeq when right segment has multiple movedSeqs", () => { + const helper = new ReconnectTestHelper(); + + // AB + // (A-C-D-(B)) + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 1, 2); + helper.obliterateRange("B", 0, 2); + // bug here: for client B, segment B had multiple movedSeqs, and when + // traversal went to the right and found a matching movedSeq in the movedSeqs + // array, it selected the lowest seq in the array, which differed from + // the correct and matching movedSeq + helper.insertText("A", 1, "C"); + helper.insertText("A", 2, "D"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("takes the correct moved client id when multiple clientIds for right segment", () => { + const helper = new ReconnectTestHelper(); + + // AB + // (A-C-D-(B)) + + helper.insertText("A", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 1, 2); + // bug here: we would incorrectly take the client id of the first element + // in the movedClientIds array because we did not take into account the + // length of _both_ the local and non-local movedSeqs arrays + helper.insertText("A", 1, "C"); + helper.obliterateRange("C", 0, 2); + helper.insertText("A", 2, "D"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("selects clientId if 0", () => { + const helper = new ReconnectTestHelper(); + + // AB + // (A-D-E-(C)-B) + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); + // bug here: client id was 0, and the check we used was !clientId, rather + // than clientId !== undefined + helper.insertText("A", 1, "C"); + helper.obliterateRange("B", 0, 2); + helper.obliterateRange("A", 1, 2); + helper.insertText("A", 1, "D"); + helper.insertText("A", 2, "E"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.B.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("obliterates unacked segment inside non-leaf-segment", () => { + const helper = new ReconnectTestHelper(); + + // FGHIJ-E-12345678-D-C-A-K-B + // FGHIJ-E-12345-(6-[7-L-8-D]-C)-A-K-B + + helper.insertText("A", 0, "AB"); + helper.insertText("A", 0, "C"); + helper.insertText("C", 0, "D"); + helper.insertText("B", 0, "12345678"); + helper.insertText("B", 0, "E"); + helper.insertText("C", 0, "FGHIJ"); + helper.insertText("A", 2, "K"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("A", 12, 15); + // bug here: when traversing for obliterate, we visit unacked segments + // within the range, considering their length 0 but still marking them + // obliterated. if the segment was inside a hiernode whose length was + // also 0, we would incorrectly skip over the entire hier node, rather + // than visiting the children segments + helper.obliterateRange("A", 11, 13); + helper.insertText("B", 13, "L"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "FGHIJE12345AKB"); + assert.equal(helper.clients.B.getText(), "FGHIJE12345AKB"); + assert.equal(helper.clients.C.getText(), "FGHIJE12345AKB"); + + helper.logger.validate(); + }); + + it("tracks length at seq of lower move/remove seq when overlapping", () => { + const helper = new ReconnectTestHelper(); + + // H-FG-A-CDE-B + // (H-F-[G-A)-C-I-D]-E-B + + helper.insertText("C", 0, "AB"); + helper.insertText("C", 1, "CDE"); + helper.insertText("B", 0, "FG"); + helper.insertText("A", 0, "H"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 0, 4); + helper.removeRange("B", 2, 6); + // bug here: this insert triggers a new chunk to be created. when the + // partial lengths of the new chunk were calculated, it incorrectly + // used the removedSeq instead of the moveSeq, despite the latter having + // occurred prior to the remove + helper.insertText("A", 1, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IEB"); + assert.equal(helper.clients.B.getText(), "IEB"); + assert.equal(helper.clients.C.getText(), "IEB"); + + helper.logger.validate(); + }); + + it("segment obliterated on insert overlaps with local obliterate", () => { + const helper = new ReconnectTestHelper(); + + // AB + // ((A-C)-B) + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("B", 1, "C"); + helper.obliterateRange("B", 0, 2); + helper.obliterateRange("A", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("obliterates entire string when concurrent inserts inside range", () => { + const helper = new ReconnectTestHelper(); + + // GT + // (G-O-S-Y-T) + + helper.insertText("B", 0, "GT"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 2); + helper.insertText("C", 1, "OY"); + helper.insertText("C", 2, "S"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("obliterate ack traverses over non-obliterated remove", () => { + const helper = new ReconnectTestHelper(); + + // ABCDEFGH-1 + // ABCDE-(F-[G]-2-H)-1 + // ABCDE-(F-[G]-2-H)-1-3 + + helper.insertText("A", 0, "1"); + helper.insertText("C", 0, "ABCDEFGH"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("C", 6, 7); + helper.insertText("A", 7, "2"); + // obliterate at seq 5 isn't getting acked because it stops traversal + // at the removed segment, which doesn't have move info + helper.obliterateRange("C", 5, 7); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("A", 6, "3"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "ABCDE13"); + assert.equal(helper.clients.C.getText(), "ABCDE13"); + + helper.logger.validate(); + }); + + it("overlapping remove and obliterate when remove happens last", () => { + const helper = new ReconnectTestHelper(); + + // FGH-E-D-BC-A + // ([F]-G)-H-E-D-B-I-C-A + + helper.insertText("A", 0, "A"); + helper.insertText("B", 0, "BC"); + helper.insertText("C", 0, "D"); + helper.insertText("B", 0, "E"); + helper.insertText("B", 0, "FGH"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 0, 2); + helper.removeRange("A", 0, 1); + helper.insertText("A", 5, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "HEDBICA"); + assert.equal(helper.clients.B.getText(), "HEDBICA"); + assert.equal(helper.clients.C.getText(), "HEDBICA"); + assert.equal(helper.clients.D.getText(), "HEDBICA"); + + helper.logger.validate(); + }); + + it("overlapping remove and obliterate when remove happens last _and_ partial length already exists", () => { + const helper = new ReconnectTestHelper(); + + // FGH-CDE-B-A + // [F-(GH)-C]-D-Z-E-B-A + + helper.insertText("B", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("B", 0, "CDE"); + helper.insertText("C", 0, "FGH"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 1, 2); + helper.removeRange("A", 0, 4); + helper.insertText("A", 1, "Z"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "DZEBA"); + + helper.logger.validate(); + }); + + it("overlapping obliterate and remove when obliterate is larger than remove and happened last", () => { + const helper = new ReconnectTestHelper(); + + // H-CDEFG-B-A + // (H-C-[D]-E)-F-Z-G-B-A + + helper.insertText("B", 0, "A"); + helper.insertText("C", 0, "B"); + helper.insertText("A", 0, "CDEFG"); + helper.insertText("A", 0, "H"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("A", 2, 3); + helper.obliterateRange("C", 0, 4); + helper.insertText("C", 1, "Z"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "FZGBA"); + + helper.logger.validate(); + }); + + it("wasMovedOnInsert remains after leaf node is split", () => { + const helper = new ReconnectTestHelper(); + + // CD-B-A + // I-(C-(G)H-E)-F-D-B-A + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("B", 0, "CD"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("B", 1, "EF"); + helper.obliterateRange("B", 0, 2); + helper.insertText("A", 1, "GH"); + helper.obliterateRange("A", 1, 2); + helper.insertText("B", 0, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IFDBA"); + + helper.logger.validate(); + }); + + it("overlapping obliterates, segment is obliterated on insert and by local client", () => { + const helper = new ReconnectTestHelper(); + + // DEFG-BC-A + // v-----------v + // v-----v + // (D-(E-H-F)-G)-B-I-C-A + + helper.insertText("B", 0, "A"); + helper.insertText("C", 0, "BC"); + helper.insertText("B", 0, "DEFG"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 1, 3); + helper.insertText("A", 2, "H"); + helper.obliterateRange("A", 0, 5); + helper.insertText("A", 1, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "BICA"); + + helper.logger.validate(); + }); + + it("overlapping obliterates and remove", () => { + const helper = new ReconnectTestHelper(); + + // FGHIJKL-BCDE-A + // (FGHI-[JK-(L-B)-C]-D)-E-M-A + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "BCDE"); + helper.insertText("A", 0, "FGHIJKL"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("B", 4, 9); + helper.obliterateRange("A", 6, 8); + helper.obliterateRange("C", 0, 10); + helper.insertText("C", 1, "M"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "EMA"); + + helper.logger.validate(); + }); + + it("does not mark obliterated on insert for non-acked obliterates", () => { + const helper = new ReconnectTestHelper(); + + // CDE-B-A + // I-((C-F)-G-D)-H-E-B-A + + helper.insertText("B", 0, "A"); + helper.insertText("A", 0, "B"); + helper.insertText("C", 0, "CDE"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("A", 1, "FG"); + helper.obliterateRange("A", 0, 2); + helper.insertText("A", 2, "H"); + helper.obliterateRange("C", 0, 2); + helper.insertText("C", 0, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IHEBA"); + + helper.logger.validate(); + }); + + it("partial len isLocal when seq is -1 but moveSeq > -1", () => { + const helper = new ReconnectTestHelper(); + + // CDEFG-AB + // C-((D-I-E)-F)-G-A-H-B + + helper.insertText("A", 0, "AB"); + helper.insertText("B", 0, "CDEFG"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 1, 4); + helper.obliterateRange("B", 1, 3); + helper.insertText("B", 4, "H"); + helper.insertText("A", 2, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "CGAHB"); + + helper.logger.validate(); + }); + + it("obliterated on insert by overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + // B-DEFG-C-A + // ((B-D-H-E)-F-I-G-C)-A + + helper.insertText("C", 0, "A"); + helper.insertText("A", 0, "BC"); + helper.insertText("A", 1, "DEFG"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 6); + helper.obliterateRange("A", 0, 3); + helper.insertText("C", 2, "H"); + helper.insertText("A", 1, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "A"); + + helper.logger.validate(); + }); + + it("overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + // ABCDEF + // v-------------v + // v-----------v + // v-------v + // I-((AB-(C-G)-H-D)-E)-F + + helper.insertText("A", 0, "ABCDEF"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 4); + helper.obliterateRange("C", 2, 5); + helper.insertText("A", 3, "GH"); + helper.insertText("C", 0, "I"); + helper.obliterateRange("A", 0, 4); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IF"); + + helper.logger.validate(); + }); + + it("overlapping obliterates", () => { + const helper = new ReconnectTestHelper(); + + // CDEF-AB + // v-------------------v + // v---------v + // v-v + // ((C-(G)-H-D)-E-I-F-A)-B + + helper.insertText("B", 0, "AB"); + helper.insertText("C", 0, "CDEF"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 5); + helper.obliterateRange("C", 0, 2); + helper.insertText("A", 1, "GH"); + helper.insertText("C", 1, "I"); + helper.obliterateRange("A", 1, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "B"); + + helper.logger.validate(); + }); + + it("overlapping remove and obliterate, local obliterate does not have a remote obliterated len", () => { + const helper = new ReconnectTestHelper(); + + // v-v------v-v + // G-(H-[F]-E)-D-[A]-B-I-C + + helper.insertText("A", 0, "ABC"); + helper.insertText("B", 0, "D"); + helper.insertText("B", 0, "E"); + helper.insertText("A", 0, "F"); + helper.removeRange("A", 0, 2); + helper.insertText("B", 0, "GH"); + helper.insertText("A", 1, "I"); + helper.obliterateRange("B", 1, 3); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "GDBIC"); + + helper.logger.validate(); + }); + + it("triple overlapping obliterate and overlapping remove", () => { + const helper = new ReconnectTestHelper(); + + // I-H-BCDEFG-A + // v------------v + // v-------v + // v-v + // v-------------------------v + // [[I]-H-BCD-(E-(F-(J)-G))-A] + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "BCDEFG"); + helper.insertText("B", 0, "H"); + helper.insertText("B", 0, "I"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 6, 8); + helper.obliterateRange("A", 5, 8); + helper.removeRange("C", 0, 1); + helper.insertText("C", 6, "J"); + helper.obliterateRange("C", 6, 7); + helper.removeRange("A", 0, 6); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + assert.equal(helper.clients.C.getText(), ""); + + helper.logger.validate(); + }); + + it("triple overlapping obliterate with one being local", () => { + const helper = new ReconnectTestHelper(); + + // CDEFG-B-A + // v--------v + // v--------v + // v------v + // J-(CD-((E-H)-F))-I-G-B-A + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("C", 0, "CDEFG"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 2, 4); + helper.insertText("C", 3, "H"); + helper.obliterateRange("C", 0, 4); + helper.insertText("C", 1, "I"); + helper.insertText("B", 0, "J"); + helper.obliterateRange("B", 3, 5); + helper.processAllOps(); + + assert.equal(helper.clients.C.getText(), "JIGBA"); + + helper.logger.validate(); + }); + + it("obliterate ack traversal is not stopped by moved segment", () => { + const helper = new ReconnectTestHelper(); + + // ABCD + // (A-(B)-E-C)-D-F + + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("A", 1, 2); + helper.insertText("C", 2, "E"); + helper.obliterateRange("A", 0, 2); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("C", 1, "F"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "DF"); + assert.equal(helper.clients.C.getText(), "DF"); + + helper.logger.validate(); + }); + + it("updates partial lengths for segments when doing obliterate ack traversal", () => { + const helper = new ReconnectTestHelper(); + + // O-JKLMN-FGHI-E-CD-AB + // v-v---v-----v + // (P-O-JKLMN-FG-[H]-Q-[I-E-C]-D-A)-B + // (P-O-JKLMN-FG-[H]-Q-[I-E-C]-D-A)-B-R + + // problematic segment: H-Q-I-E + + helper.insertText("B", 0, "AB"); + helper.insertText("A", 0, "CD"); + helper.insertText("A", 0, "E"); + helper.insertText("A", 0, "FGHI"); + helper.insertText("C", 0, "JKLMN"); + helper.insertText("B", 0, "O"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("A", 0, "P"); + helper.insertText("B", 9, "Q"); + helper.removeRange("A", 9, 13); + helper.obliterateRange("A", 0, 11); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("B", 1, "R"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "BR"); + + helper.logger.validate(); + }); + + // fails only for incremental + it("combines remote obliterated length ", () => { + const helper = new ReconnectTestHelper(); + + // R-XYZ12-STUVW-LMNOP-DEFGHIJK-A-34567890-Q-BC + // v---------------v------------v------v--------v------------v + // [R-XYZ12-STUVW-L]-abcdefghij-[MNOP-D]-klmnop-[EFGHIJK-A-34]-5-(6-x-7)-890-Q-BC + + // v--------v + // v--------v + // a-T-b-S-c-[def]-ghij-k-Q-l-R-mno-[p-5-8-[9]-0-Q-B]-C + + // v--------v + // v--------v + // segment is R-mno-[p-5-8-[9]-0-Q-B]-C + // v-------------------------------v + // v--------v + // v------------v + // R-mno-[p-[EFGHIJK-A-34]-5-(6-x-7)-8-[9]-0-Q-B]-C + + helper.insertText("A", 0, "ABC"); + helper.insertText("C", 0, "DEFGHIJK"); + helper.insertText("C", 0, "LMNOP"); + helper.insertText("A", 1, "Q"); + helper.insertText("B", 0, "RSTUVW"); + helper.insertText("B", 1, "XYZ12"); + helper.insertText("A", 1, "34567890"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("C", 29, "x"); + helper.removeRange("B", 0, 27); + helper.insertText("A", 12, "abcdefghij"); + helper.obliterateRange("A", 38, 40); + helper.insertText("C", 17, "klmnop"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "abcdefghijklmnop5890QBC"); + + helper.logger.validate(); + helper.removeRange("B", 3, 6); + helper.insertText("C", 11, "Q"); + helper.insertText("B", 9, "R"); + helper.insertText("B", 2, "S"); + helper.removeRange("C", 19, 23); + helper.obliterateRange("B", 14, 18); + helper.insertText("B", 1, "T"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "aTbScghijkQlRmnoC"); + + helper.logger.validate(); + }); + + it("three obliterates on same segment", () => { + const helper = new ReconnectTestHelper(); + + // A + // (C-B-D-((A))) + + helper.insertText("B", 0, "A"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 0, 1); + helper.obliterateRange("A", 0, 1); + helper.insertText("A", 0, "B"); + helper.insertText("C", 0, "C"); + helper.insertText("A", 1, "D"); + helper.obliterateRange("C", 0, 2); + helper.processAllOps(); + helper.logger.validate(); + }); + + describe("incremental partial length updates", () => { + it("obliterates concurrently inserted segment", () => { + const helper = new ReconnectTestHelper(); + + // (C-B-A) + + helper.insertText("A", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("A", 0, "C"); + helper.obliterateRange("A", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("obliterates 2 concurrently inserted segments", () => { + const helper = new ReconnectTestHelper(); + + // (C-B-D-A) + + helper.insertText("B", 0, "A"); + helper.insertText("A", 0, "B"); + helper.insertText("B", 0, "C"); + helper.obliterateRange("B", 0, 2); + helper.insertText("A", 1, "D"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("obliterates 4 concurrently inserted segments", () => { + const helper = new ReconnectTestHelper(); + + // I-F-(G-D-H-E-C-A)-B + + helper.insertText("B", 0, "AB"); + helper.insertText("B", 0, "C"); + helper.insertText("A", 0, "DE"); + helper.insertText("B", 0, "FG"); + helper.obliterateRange("B", 1, 4); + helper.insertText("A", 1, "H"); + helper.insertText("B", 0, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IFB"); + + helper.logger.validate(); + }); + + it("obliterates 3 concurrently inserted segments", () => { + const helper = new ReconnectTestHelper(); + + // I-G-(H-D-F-E-B)-C-A + + helper.insertText("B", 0, "A"); + helper.insertText("B", 0, "BC"); + helper.insertText("A", 0, "DE"); + helper.insertText("A", 1, "F"); + helper.insertText("B", 0, "GH"); + helper.obliterateRange("B", 1, 3); + helper.insertText("B", 0, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IGCA"); + + helper.logger.validate(); + }); + + it("overlapping remove + obliterate, remove happened first", () => { + const helper = new ReconnectTestHelper(); + + // D-EFG-B-H-C-A + // I-D-([E]-F)-G-B-H-C-A + + helper.insertText("A", 0, "A"); + helper.insertText("B", 0, "BC"); + helper.insertText("C", 0, "DEFG"); + helper.insertText("B", 1, "H"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("B", 1, 2); + helper.obliterateRange("A", 1, 3); + helper.insertText("A", 0, "I"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "IDGBHCA"); + + helper.logger.validate(); + }); + + it("overlapping remove + obliterate, remove happened last", () => { + const helper = new ReconnectTestHelper(); + + // DEFGH-C-B-A + // [D]-[E-(F)-G]-H-C-B-A + + helper.insertText("C", 0, "A"); + helper.insertText("B", 0, "B"); + helper.insertText("A", 0, "C"); + helper.insertText("B", 0, "DEFGH"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 2, 3); + helper.removeRange("A", 1, 4); + helper.removeRange("A", 0, 1); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "HCBA"); + + helper.logger.validate(); + }); + + it("segment inside locally obliterated segment group is split", () => { + const helper = new ReconnectTestHelper(); + + // CDEF-AB + // [C]-DE-(F-(G)-H-A)-B + // [C]-DE-(F-(G)-H-A)-(B) + + helper.insertText("B", 0, "AB"); + helper.insertText("B", 0, "CDEF"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("C", 0, 1); + helper.insertText("C", 3, "GH"); + helper.obliterateRange("C", 3, 4); + helper.obliterateRange("A", 3, 5); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("B", 2, 3); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "DE"); + + helper.logger.validate(); + }); + + it("continues traversal past locally removed segment inserted before first obliterate", () => { + const helper = new ReconnectTestHelper(); + + // ABC + // (E-D-(A)-[B]-C) + + helper.insertText("A", 0, "ABC"); + helper.processAllOps(); + helper.logger.validate(); + helper.obliterateRange("C", 0, 1); + helper.insertText("B", 0, "D"); + helper.removeRange("C", 0, 1); + helper.insertText("C", 0, "E"); + helper.obliterateRange("C", 0, 2); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("keeps track of remote obliterated length when hier node contains hier nodes", () => { + const helper = new ReconnectTestHelper(); + + // LMNO-HIJ-E-K-FG-D-C-AB + // lmnopq-L-[M]-NO-H-ghijk-I-(J-E-K-FG-D-C-A-a)-bc-ef-d-B + // v--------------------------------------v + // v-------------------v + // r-l-x-mn-[opq-L-[M]-NO-H-gh-s]-t-(u-i-[j]-k-I-(J-E-K-FG-D-C-A-a)-b-v-c-e)-f-d-B + + // bad segment: (I-(J-E-K-FG-D-C-A-a)-b-v-c-e)-f-d-B + + helper.insertText("B", 0, "AB"); + helper.insertText("C", 0, "C"); + helper.insertText("A", 0, "D"); + helper.insertText("B", 0, "EFG"); + helper.insertText("C", 0, "HIJ"); + helper.insertText("B", 1, "K"); + helper.insertText("B", 0, "LMNO"); + helper.processAllOps(); + helper.logger.validate(); + helper.removeRange("A", 1, 2); + helper.insertText("A", 13, "abcd"); + helper.obliterateRange("A", 5, 14); + helper.insertText("A", 7, "ef"); // seq: 11, len: 7 + helper.insertText("B", 5, "ghijk"); + helper.insertText("C", 0, "lmnopq"); // seq: 13, len: 7 + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "lmnopqLNOHghijkIbcefdB"); + + helper.logger.validate(); + helper.insertText("A", 0, "r"); + helper.insertText("B", 12, "stu"); + helper.insertText("A", 18, "v"); // seq: 16, len: 8 + helper.obliterateRange("B", 14, 22); // seq: 17, len: 3 + helper.removeRange("B", 3, 13); // seq: 18, len: 3 + helper.removeRange("A", 14, 15); // seq: 19, len: 3 + helper.insertText("B", 1, "x"); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "rlxmntfdB"); + + helper.logger.validate(); + }); + + it("combines remote obliterated length for parent node of tree with depth >=3", () => { + const helper = new ReconnectTestHelper(); + + // VWXYZ0-(K)-[L]-M-[N]-O-EFGHIJ-[A]-B-P-TU-QRS-CD + // v-v------v------------v + // v--------------------v + // v----v---------v-v-----v-----v------------------------------v-v + // V-c-W-[XYZ0]-(K)-[L]-[M]-[N]-[O-EFG]-[H]-a-[b-[IJ-[A]-B-P-T]-U-Q]-[R]-S-C-d-f-e-D + + // v-v------v-v---v-----------v + // v-------------------------v + // v------v---------v-v-----v---v---v--v-----------------------------------v-v + // v---------v + // j-i-(V-c-W-[XY)-Z0]-(K)-[L]-[M]-[N]-[O-E]-h-[FG]-[H]-a-[b-[I]-g-[J-[A]-B-P-T]-U-Q]-[R]-S-C-d-f-e-D + + // problem segment: j-i-(V-c-W-[XY)-Z0]-(K)-[L]-[M]-[N]-[O-E]-h-[FG]-[H]-a-[b] + // specifically: j-(V-c-W + + helper.insertText("C", 0, "ABCD"); + helper.removeRange("C", 0, 1); + helper.insertText("B", 0, "EFGHIJ"); + helper.insertText("A", 0, "KLMNO"); + helper.obliterateRange("A", 0, 1); + helper.removeRange("A", 0, 1); + helper.insertText("C", 1, "PQRS"); + helper.insertText("C", 2, "TU"); + helper.removeRange("A", 1, 2); + helper.insertText("A", 0, "VWXYZ0"); // seq: 10, len: 2 + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "VWXYZ0MOEFGHIJBPTUQRSCD"); + + helper.logger.validate(); + helper.insertText("C", 12, "ab"); + helper.removeRange("B", 11, 17); + helper.removeRange("C", 13, 21); + helper.insertText("B", 1, "c"); // seq: 14, len: 3 + helper.insertText("C", 16, "de"); + helper.insertText("C", 17, "f"); + helper.removeRange("B", 3, 15); + helper.insertText("A", 13, "g"); + helper.insertText("A", 9, "h"); + helper.obliterateRange("C", 0, 4); // seq: 20, len: 0 + helper.insertText("C", 0, "i"); + helper.insertText("A", 0, "j"); // seq: 22, len: 1 + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "jihagSCdfeD"); + + helper.logger.validate(); + }); + }); + }); +} diff --git a/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts b/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts index 266016379388..9df0a6984acd 100644 --- a/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts @@ -5,7 +5,7 @@ import { strict as assert } from "assert"; import { MergeTreeDeltaType } from "../ops"; -import { PartialSequenceLengths, verify } from "../partialLengths"; +import { PartialSequenceLengths, verify, verifyExpected } from "../partialLengths"; import { TestClient } from "./testClient"; import { insertText, validatePartialLengths } from "./testUtils"; @@ -17,7 +17,10 @@ describe("obliterate partial lengths", () => { beforeEach(() => { PartialSequenceLengths.options.verifier = verify; - client = new TestClient(); + PartialSequenceLengths.options.verifyExpected = verifyExpected; + client = new TestClient({ + mergeTreeEnableObliterate: true, + }); client.startOrUpdateCollaboration("local"); for (const char of "hello world") { client.applyMsg( @@ -37,7 +40,7 @@ describe("obliterate partial lengths", () => { it("removes text", () => { assert.equal(client.getText(), "hello world"); - const localObliterateOp = client.obliterateRangeLocal(0, client.getLength()); + const localObliterateOp = client.obliterateRangeLocal(0, "hello world".length); assert.equal(client.getText(), ""); validatePartialLengths(localClientId, client.mergeTree, [ @@ -74,7 +77,9 @@ describe("obliterate partial lengths", () => { }); it("is correct for different heights", () => { - client = new TestClient(); + client = new TestClient({ + mergeTreeEnableObliterate: true, + }); client.startOrUpdateCollaboration("local"); for (let i = 0; i < 100; i++) { @@ -118,8 +123,8 @@ describe("obliterate partial lengths", () => { }); validatePartialLengths(localClientId, client.mergeTree, [ - { seq: refSeq, len: "hello world".length }, - { seq: refSeq + 1, len: "world".length }, + { seq: refSeq, len: "hello world".length, localSeq: refSeq }, + { seq: refSeq + 1, len: "world".length, localSeq: refSeq + 1 }, ]); client.applyMsg(client.makeOpMessage(localRemoveOp, refSeq + 1)); @@ -249,7 +254,7 @@ describe("obliterate partial lengths", () => { overwrite: false, opArgs: undefined as any, }); - const localObliterateOp = client.obliterateRangeLocal(0, "hello ".length); + const localObliterateOp = client.obliterateRangeLocal(0, "hello".length); validatePartialLengths(localClientId, client.mergeTree, [ { seq: refSeq, len: "hello world".length }, @@ -272,15 +277,15 @@ describe("obliterate partial lengths", () => { }); }); - describe.skip("obliterate with concurrent inserts", () => { + describe("obliterate with concurrent inserts", () => { it("obliterates when concurrent insert in middle of string", () => { - client.obliterateRangeLocal(0, client.getLength()); + const localObliterateOp = client.obliterateRangeLocal(0, client.getLength()); insertText({ mergeTree: client.mergeTree, pos: "hello".length, refSeq, clientId: remoteClientId, - seq: refSeq + 2, + seq: refSeq + 1, text: "more ", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, @@ -289,51 +294,90 @@ describe("obliterate partial lengths", () => { validatePartialLengths(localClientId, client.mergeTree, [ { seq: refSeq, len: "hello world".length }, - { seq: refSeq + 1, len: "".length }, - { seq: refSeq + 2, len: "".length }, + { seq: refSeq + 1, len: "hellomore world".length }, + { seq: refSeq + 1, len: "".length, localSeq: refSeq + 1 }, ]); + + client.applyMsg(client.makeOpMessage(localObliterateOp, refSeq + 2)); + + validatePartialLengths( + remoteClientId, + client.mergeTree, + [ + { seq: refSeq, len: "hello world".length }, + { seq: refSeq + 1, len: "hellomore world".length }, + { seq: refSeq + 2, len: "".length, localSeq: refSeq + 2 }, + ], + refSeq, + ); }); it("obliterate does not affect concurrent insert at start of string", () => { - client.obliterateRangeLocal(0, client.getLength()); + const localObliterateOp = client.obliterateRangeLocal(0, client.getLength()); insertText({ mergeTree: client.mergeTree, pos: 0, refSeq, clientId: remoteClientId, - seq: refSeq + 2, + seq: refSeq + 1, text: "more ", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, }); - assert.equal(client.getText(), ""); + assert.equal(client.getText(), "more "); validatePartialLengths(localClientId, client.mergeTree, [ { seq: refSeq, len: "hello world".length }, - { seq: refSeq + 1, len: "".length }, - { seq: refSeq + 2, len: "more ".length }, + { seq: refSeq + 1, len: "more hello world".length }, + { seq: refSeq + 1, len: "more ".length, localSeq: refSeq + 1 }, ]); + + client.applyMsg(client.makeOpMessage(localObliterateOp, refSeq + 2)); + + validatePartialLengths( + remoteClientId, + client.mergeTree, + [ + { seq: refSeq, len: "hello world".length }, + { seq: refSeq + 1, len: "more hello world".length }, + { seq: refSeq + 2, len: "more ".length }, + ], + refSeq, + ); }); it("obliterate does not affect concurrent insert at end of string", () => { - client.obliterateRangeLocal(0, client.getLength()); + const localObliterateOp = client.obliterateRangeLocal(0, client.getLength()); insertText({ mergeTree: client.mergeTree, pos: "hello world".length, refSeq, clientId: remoteClientId, - seq: refSeq + 2, + seq: refSeq + 1, text: "more ", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, }); - assert.equal(client.getText(), ""); + assert.equal(client.getText(), "more "); validatePartialLengths(localClientId, client.mergeTree, [ { seq: refSeq, len: "hello world".length }, - { seq: refSeq + 1, len: "".length }, - { seq: refSeq + 2, len: "more ".length }, + { seq: refSeq + 1, len: "hello worldmore ".length }, + { seq: refSeq + 1, len: "more ".length, localSeq: refSeq + 1 }, ]); + + client.applyMsg(client.makeOpMessage(localObliterateOp, refSeq + 2)); + + validatePartialLengths( + remoteClientId, + client.mergeTree, + [ + { seq: refSeq, len: "hello world".length }, + { seq: refSeq + 1, len: "hello worldmore ".length }, + { seq: refSeq + 2, len: "more ".length }, + ], + refSeq, + ); }); }); }); diff --git a/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts b/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts index b77db2ca7a7a..a44f8734d734 100644 --- a/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts @@ -4,146 +4,211 @@ */ import { strict as assert } from "assert"; -import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IMergeTreeDeltaOp } from "../ops"; -import { createClientsAtInitialState, TestClientLogger } from "./testClientLogger"; - -const ClientIds = ["A", "B", "C", "D"] as const; -type ClientName = (typeof ClientIds)[number]; - -class ReconnectTestHelper { - clients = createClientsAtInitialState({ initialState: "" }, ...ClientIds); - - idxFromName(name: ClientName): number { - return name.charCodeAt(0) - "A".charCodeAt(0); - } - - logger = new TestClientLogger(this.clients.all); - - ops: ISequencedDocumentMessage[] = []; - perClientOps: ISequencedDocumentMessage[][] = this.clients.all.map(() => []); - - seq: number = 0; - - public insertText(clientName: ClientName, pos: number, text: string): void { - const client = this.clients[clientName]; - this.ops.push(client.makeOpMessage(client.insertTextLocal(pos, text), ++this.seq)); - } - - public removeRange(clientName: ClientName, start: number, end: number): void { - const client = this.clients[clientName]; - this.ops.push(client.makeOpMessage(client.removeRangeLocal(start, end), ++this.seq)); - } - - public obliterateRange(clientName: ClientName, start: number, end: number): void { - const client = this.clients[clientName]; - this.ops.push(client.makeOpMessage(client.obliterateRangeLocal(start, end), ++this.seq)); - } - - public disconnect(clientNames: ClientName[]): void { - const clientIdxs = clientNames.map(this.idxFromName); - this.ops - .splice(0) - .forEach((op) => - this.clients.all.forEach((c, i) => - clientIdxs.includes(i) ? this.perClientOps[i].push(op) : c.applyMsg(op), - ), - ); - } - - public processAllOps(): void { - this.ops.splice(0).forEach((op) => this.clients.all.forEach((c) => c.applyMsg(op))); - } - - public reconnect(clientNames: ClientName[]): void { - const clientIdxs = clientNames.map(this.idxFromName); - this.perClientOps.forEach((clientOps, i) => { - if (clientIdxs.includes(i)) { - clientOps.splice(0).forEach((op) => this.clients.all[i].applyMsg(op)); - } +import { PartialSequenceLengths, verify, verifyExpected } from "../partialLengths"; +import { MergeTree } from "../mergeTree"; +import { ReconnectTestHelper } from "./reconnectHelper"; + +for (const incremental of [true, false]) { + describe(`obliterate partial lengths incremental = ${incremental}`, () => { + beforeEach(() => { + PartialSequenceLengths.options.verifier = verify; + PartialSequenceLengths.options.verifyExpected = verifyExpected; + MergeTree.options.incrementalUpdate = incremental; }); - } - - public submitDisconnectedOp(clientName: ClientName, op: IMergeTreeDeltaOp): void { - const client = this.clients[clientName]; - const pendingSegmentGroups = client.peekPendingSegmentGroups(); - assert(pendingSegmentGroups); - this.ops.push( - client.makeOpMessage(client.regeneratePendingOp(op, pendingSegmentGroups), ++this.seq), - ); - } -} -describe("obliterate", () => { - it("obliterate does not expand during rebase", () => { - const helper = new ReconnectTestHelper(); + afterEach(() => { + PartialSequenceLengths.options.verifier = undefined; + PartialSequenceLengths.options.verifyExpected = undefined; + MergeTree.options.incrementalUpdate = true; + }); - helper.insertText("B", 0, "ABCD"); - helper.processAllOps(); - helper.removeRange("B", 0, 3); - helper.disconnect(["C"]); - const cOp = helper.clients.C.obliterateRangeLocal(0, 1); - assert(cOp); - helper.reconnect(["C"]); - helper.submitDisconnectedOp("C", cOp); - helper.processAllOps(); + it("obliterate does not expand during rebase", () => { + const helper = new ReconnectTestHelper(); - assert.equal(helper.clients.A.getText(), "D"); + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.removeRange("B", 0, 3); + helper.disconnect(["C"]); + const cOp = helper.obliterateRangeLocal("C", 0, 1); + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", cOp); + helper.processAllOps(); - helper.logger.validate(); - }); + assert.equal(helper.clients.A.getText(), "D"); - it.skip("deletes reconnected insert into obliterate range", () => { - const helper = new ReconnectTestHelper(); + helper.logger.validate(); + }); - helper.insertText("B", 0, "ABCD"); - helper.processAllOps(); - helper.obliterateRange("B", 0, 3); - helper.disconnect(["C"]); - const cOp = helper.clients.C.insertTextLocal(2, "aaa"); - assert(cOp); - helper.reconnect(["C"]); - helper.submitDisconnectedOp("C", cOp); - helper.processAllOps(); + it("does not delete reconnected insert into obliterate range if insert is rebased", () => { + const helper = new ReconnectTestHelper(); - assert.equal(helper.clients.A.getText(), "D"); + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 3); + helper.disconnect(["C"]); + const cOp = helper.insertTextLocal("C", 2, "aaa"); + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", cOp); + helper.processAllOps(); - helper.logger.validate(); - }); + assert.equal(helper.clients.A.getText(), "aaaD"); + assert.equal(helper.clients.C.getText(), "aaaD"); - it("does not delete reconnected insert at start of obliterate range", () => { - const helper = new ReconnectTestHelper(); + helper.logger.validate(); + }); - helper.insertText("B", 0, "ABCD"); - helper.processAllOps(); - helper.obliterateRange("B", 0, 3); - helper.disconnect(["C"]); - const cOp = helper.clients.C.insertTextLocal(0, "aaa"); - assert(cOp); - helper.reconnect(["C"]); - helper.submitDisconnectedOp("C", cOp); - helper.processAllOps(); + it("deletes reconnected insert into obliterate range when entire string deleted if rebased", () => { + const helper = new ReconnectTestHelper(); - assert.equal(helper.clients.A.getText(), "aaaD"); + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 4); + helper.disconnect(["C"]); + const cOp = helper.insertTextLocal("C", 2, "aaa"); + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", cOp); + helper.processAllOps(); - helper.logger.validate(); - }); + assert.equal(helper.clients.A.getText(), "aaa"); + assert.equal(helper.clients.C.getText(), "aaa"); + + helper.logger.validate(); + }); + + it("obliterates local segment while disconnected", () => { + const helper = new ReconnectTestHelper(); + + // [C]-D-(E)-F-H-G-B-A + + helper.insertText("B", 0, "A"); + + helper.disconnect(["C"]); + const op0 = helper.insertTextLocal("C", 0, "B"); + const op1 = helper.insertTextLocal("C", 0, "CDEFG"); + const op2 = helper.removeRangeLocal("C", 0, 1); + const op3 = helper.obliterateRangeLocal("C", 1, 2); + const op4 = helper.insertTextLocal("C", 2, "H"); + + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", op0); + helper.submitDisconnectedOp("C", op1); + helper.submitDisconnectedOp("C", op2); + helper.submitDisconnectedOp("C", op3); + helper.submitDisconnectedOp("C", op4); + + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "DFHGBA"); + + helper.logger.validate(); + }); + + it("deletes concurrently inserted segment between separated group ops", () => { + const helper = new ReconnectTestHelper(); + + // B-A + // (B-C-A) + + helper.insertText("A", 0, "A"); + helper.insertText("A", 0, "B"); + helper.processAllOps(); + helper.logger.validate(); + helper.insertText("A", 1, "C"); + + helper.disconnect(["B"]); + const op = helper.obliterateRangeLocal("B", 0, 2); + helper.reconnect(["B"]); + helper.submitDisconnectedOp("B", op); + + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("removes correct number of pending segments", () => { + const helper = new ReconnectTestHelper(); + + // (BC)-[A] + + const op0 = helper.insertTextLocal("A", 0, "A"); + const op1 = helper.insertTextLocal("A", 1, "BC"); + const op2 = helper.obliterateRangeLocal("A", 0, 2); + + helper.submitDisconnectedOp("A", op0); + helper.submitDisconnectedOp("A", op1); + helper.submitDisconnectedOp("A", op2); + + helper.removeRange("A", 0, 1); + + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), ""); + + helper.logger.validate(); + }); + + it("doesn't do obliterate ack traversal when starting segment has been acked", () => { + const helper = new ReconnectTestHelper(); + + // AB + // (E)-[F]-(G-D-(C-A)-B) + + helper.insertText("B", 0, "AB"); + helper.processAllOps(); + helper.logger.validate(); - it("does not delete reconnected insert at end of obliterate range", () => { - const helper = new ReconnectTestHelper(); + const op0 = helper.insertTextLocal("A", 0, "C"); + const op1 = helper.obliterateRangeLocal("A", 0, 2); + helper.submitDisconnectedOp("A", op0); + helper.submitDisconnectedOp("A", op1); - helper.insertText("B", 0, "ABCD"); - helper.processAllOps(); - helper.obliterateRange("B", 0, 3); - helper.disconnect(["C"]); - const cOp = helper.clients.C.insertTextLocal(3, "aaa"); - assert(cOp); - helper.reconnect(["C"]); - helper.submitDisconnectedOp("C", cOp); - helper.processAllOps(); + helper.insertText("B", 0, "D"); + helper.insertText("A", 0, "EFG"); + helper.obliterateRange("A", 0, 1); + helper.removeRange("A", 0, 1); + helper.obliterateRange("A", 0, 2); + helper.processAllOps(); - assert.equal(helper.clients.A.getText(), "aaaD"); + assert.equal(helper.clients.A.getText(), ""); - helper.logger.validate(); + helper.logger.validate(); + }); + + it("does not delete reconnected insert at start of obliterate range if rebased", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 3); + helper.disconnect(["C"]); + const cOp = helper.insertTextLocal("C", 0, "aaa"); + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", cOp); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "aaaD"); + assert.equal(helper.clients.C.getText(), "aaaD"); + + helper.logger.validate(); + }); + + it("does not delete reconnected insert at end of obliterate range", () => { + const helper = new ReconnectTestHelper(); + + helper.insertText("B", 0, "ABCD"); + helper.processAllOps(); + helper.obliterateRange("B", 0, 3); + helper.disconnect(["C"]); + const cOp = helper.insertTextLocal("C", 3, "aaa"); + helper.reconnect(["C"]); + helper.submitDisconnectedOp("C", cOp); + helper.processAllOps(); + + assert.equal(helper.clients.A.getText(), "aaaD"); + + helper.logger.validate(); + }); }); -}); +} diff --git a/packages/dds/merge-tree/src/test/obliterate.spec.ts b/packages/dds/merge-tree/src/test/obliterate.spec.ts index 8d68f0825197..68ea6577459a 100644 --- a/packages/dds/merge-tree/src/test/obliterate.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.spec.ts @@ -4,19 +4,20 @@ */ import { strict as assert } from "assert"; -import { UnassignedSequenceNumber } from "../constants"; import { MergeTreeDeltaType } from "../ops"; import { TestClient } from "./testClient"; import { insertText } from "./testUtils"; -describe.skip("obliterate", () => { +describe("obliterate", () => { let client: TestClient; let refSeq: number; const localClientId = 17; - const remoteClientId = 18; + const remoteClientId = localClientId + 1; beforeEach(() => { - client = new TestClient(); + client = new TestClient({ + mergeTreeEnableObliterate: true, + }); client.startOrUpdateCollaboration("local"); for (const char of "hello world") { client.applyMsg( @@ -31,15 +32,7 @@ describe.skip("obliterate", () => { }); it("removes text", () => { - client.obliterateRange({ - start: 0, - end: client.getLength(), - refSeq, - clientId: localClientId, - seq: refSeq + 1, - overwrite: false, - opArgs: undefined as any, - }); + client.obliterateRangeLocal(0, client.getLength()); assert.equal(client.getText(), ""); }); @@ -56,7 +49,7 @@ describe.skip("obliterate", () => { }); insertText({ mergeTree: client.mergeTree, - pos: 0, + pos: 1, refSeq, clientId: remoteClientId + 1, seq: refSeq + 2, @@ -66,10 +59,10 @@ describe.skip("obliterate", () => { }); assert.equal(client.getText(), ""); }); - it("removes text for insert then obliterate", () => { + it("removes text for insert then obliterate when deleting entire string", () => { insertText({ mergeTree: client.mergeTree, - pos: 0, + pos: 1, refSeq, clientId: remoteClientId + 1, seq: refSeq + 1, @@ -88,13 +81,35 @@ describe.skip("obliterate", () => { }); assert.equal(client.getText(), ""); }); + it("removes text for insert then obliterate", () => { + insertText({ + mergeTree: client.mergeTree, + pos: 5, + refSeq, + clientId: remoteClientId + 1, + seq: refSeq + 1, + text: "more ", + props: undefined, + opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, + }); + client.obliterateRange({ + start: 1, + end: "hello world".length, + refSeq, + clientId: remoteClientId, + seq: refSeq + 2, + overwrite: false, + opArgs: undefined as any, + }); + assert.equal(client.getText(), "h"); + }); }); describe("endpoint behavior", () => { it("does not expand to include text inserted at start", () => { client.obliterateRange({ start: 5, - end: client.getLength(), + end: "hello world".length, refSeq, clientId: remoteClientId, seq: refSeq + 1, @@ -107,16 +122,16 @@ describe.skip("obliterate", () => { refSeq, clientId: remoteClientId + 1, seq: refSeq + 2, - text: " world", + text: "XXX", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, }); - assert.equal(client.getText(), "hello world"); + assert.equal(client.getText(), "helloXXX"); }); it("does not expand to include text inserted at end", () => { client.obliterateRange({ start: 0, - end: 5, + end: "hello".length, refSeq, clientId: remoteClientId, seq: refSeq + 1, @@ -129,32 +144,24 @@ describe.skip("obliterate", () => { refSeq, clientId: remoteClientId + 1, seq: refSeq + 2, - text: "hello", + text: "XXX", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, }); - assert.equal(client.getText(), "hello world"); + assert.equal(client.getText(), "XXX world"); }); }); describe("local obliterate with concurrent inserts", () => { it("removes range when pending local obliterate op", () => { - client.obliterateRange({ - start: 0, - end: "hello world".length, - refSeq, - clientId: localClientId, - seq: UnassignedSequenceNumber, - overwrite: false, - opArgs: undefined as any, - }); + client.obliterateRangeLocal(0, "hello world".length); insertText({ mergeTree: client.mergeTree, - pos: 0, + pos: 1, refSeq, clientId: remoteClientId, seq: refSeq + 2, - text: "more ", + text: "XXX", props: undefined, opArgs: { op: { type: MergeTreeDeltaType.INSERT } }, }); diff --git a/packages/dds/merge-tree/src/test/ordinal.spec.ts b/packages/dds/merge-tree/src/test/ordinal.spec.ts index 76bd08dfb5d2..d3a295d8b97b 100644 --- a/packages/dds/merge-tree/src/test/ordinal.spec.ts +++ b/packages/dds/merge-tree/src/test/ordinal.spec.ts @@ -3,9 +3,15 @@ * Licensed under the MIT License. */ import assert from "assert"; -import { computeNumericOrdinal, computeHierarchicalOrdinal } from "../ordinal"; +import { computeHierarchicalOrdinal } from "../ordinal"; import { doOverRange } from "./mergeTreeOperationRunner"; +function computeNumericOrdinal(index: number) { + const prefixLen = Math.floor(index / 0xffff); + const prefix = String.fromCharCode(0xffff).repeat(prefixLen); + return `${prefix}${String.fromCharCode(index - prefixLen * 0xffff)}`; +} + describe("MergeTree.ordinals", () => { doOverRange( { min: 1, max: 16 }, diff --git a/packages/dds/merge-tree/src/test/partialLength.spec.ts b/packages/dds/merge-tree/src/test/partialLength.spec.ts index 7c03cfd22746..c5f868018954 100644 --- a/packages/dds/merge-tree/src/test/partialLength.spec.ts +++ b/packages/dds/merge-tree/src/test/partialLength.spec.ts @@ -6,7 +6,7 @@ import { UnassignedSequenceNumber } from "../constants"; import { MergeTree } from "../mergeTree"; import { MergeTreeDeltaType } from "../ops"; -import { PartialSequenceLengths, verify } from "../partialLengths"; +import { PartialSequenceLengths, verify, verifyExpected } from "../partialLengths"; import { TextSegment } from "../textSegment"; import { insertSegments, insertText, markRangeRemoved, validatePartialLengths } from "./testUtils"; @@ -18,6 +18,7 @@ describe("partial lengths", () => { beforeEach(() => { PartialSequenceLengths.options.verifier = verify; + PartialSequenceLengths.options.verifyExpected = verifyExpected; mergeTree = new MergeTree(); insertSegments({ mergeTree, @@ -34,6 +35,7 @@ describe("partial lengths", () => { afterEach(() => { PartialSequenceLengths.options.verifier = undefined; + PartialSequenceLengths.options.verifyExpected = undefined; }); it("passes with no additional ops", () => { diff --git a/packages/dds/merge-tree/src/test/reconnectHelper.ts b/packages/dds/merge-tree/src/test/reconnectHelper.ts new file mode 100644 index 000000000000..c15604bcb935 --- /dev/null +++ b/packages/dds/merge-tree/src/test/reconnectHelper.ts @@ -0,0 +1,114 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "assert"; +import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; +import { IMergeTreeDeltaOp } from "../ops"; +import { SegmentGroup } from ".."; +import { createClientsAtInitialState, TestClientLogger } from "./testClientLogger"; + +const ClientIds = ["A", "B", "C", "D"] as const; +type ClientName = (typeof ClientIds)[number]; + +export class ReconnectTestHelper { + clients = createClientsAtInitialState( + { + initialState: "", + options: { mergeTreeEnableObliterate: true }, + }, + ...ClientIds, + ); + + idxFromName(name: ClientName): number { + return name.charCodeAt(0) - "A".charCodeAt(0); + } + + logger = new TestClientLogger(this.clients.all); + + ops: ISequencedDocumentMessage[] = []; + perClientOps: ISequencedDocumentMessage[][] = this.clients.all.map(() => []); + + seq: number = 0; + + public insertText(clientName: ClientName, pos: number, text: string): void { + const client = this.clients[clientName]; + this.ops.push(client.makeOpMessage(client.insertTextLocal(pos, text), ++this.seq)); + } + + public removeRange(clientName: ClientName, start: number, end: number): void { + const client = this.clients[clientName]; + this.ops.push(client.makeOpMessage(client.removeRangeLocal(start, end), ++this.seq)); + } + + public obliterateRange(clientName: ClientName, start: number, end: number): void { + const client = this.clients[clientName]; + this.ops.push(client.makeOpMessage(client.obliterateRangeLocal(start, end), ++this.seq)); + } + + public insertTextLocal(clientName: ClientName, pos: number, text: string) { + const client = this.clients[clientName]; + const op = client.insertTextLocal(pos, text); + assert(op); + const seg = client.peekPendingSegmentGroups(); + assert(seg); + return { op, seg, refSeq: client.getCollabWindow().currentSeq }; + } + + public removeRangeLocal(clientName: ClientName, start: number, end: number) { + const client = this.clients[clientName]; + const op = client.removeRangeLocal(start, end); + assert(op); + const seg = client.peekPendingSegmentGroups(); + assert(seg); + return { op, seg, refSeq: client.getCollabWindow().currentSeq }; + } + + public obliterateRangeLocal(clientName: ClientName, start: number, end: number) { + const client = this.clients[clientName]; + const op = client.obliterateRangeLocal(start, end); + assert(op); + const seg = client.peekPendingSegmentGroups(); + assert(seg); + return { op, seg, refSeq: client.getCollabWindow().currentSeq }; + } + + public disconnect(clientNames: ClientName[]): void { + const clientIdxs = clientNames.map(this.idxFromName); + this.ops + .splice(0) + .forEach((op) => + this.clients.all.forEach((c, i) => + clientIdxs.includes(i) ? this.perClientOps[i].push(op) : c.applyMsg(op), + ), + ); + } + + public processAllOps(): void { + this.ops.splice(0).forEach((op) => + this.clients.all.forEach((c) => { + c.applyMsg(op); + }), + ); + } + + public reconnect(clientNames: ClientName[]): void { + const clientIdxs = clientNames.map(this.idxFromName); + this.perClientOps.forEach((clientOps, i) => { + if (clientIdxs.includes(i)) { + clientOps.splice(0).forEach((op) => this.clients.all[i].applyMsg(op)); + } + }); + } + + public submitDisconnectedOp( + clientName: ClientName, + op: { op: IMergeTreeDeltaOp; seg: SegmentGroup | SegmentGroup[]; refSeq: number }, + ): void { + const client = this.clients[clientName]; + this.ops.push( + client.makeOpMessage(client.regeneratePendingOp(op.op, op.seg), ++this.seq, op.refSeq), + ); + } +} diff --git a/packages/dds/merge-tree/src/test/resetPendingSegmentsToOp.spec.ts b/packages/dds/merge-tree/src/test/resetPendingSegmentsToOp.spec.ts index 82bc700898b0..dbbb0d7f1f77 100644 --- a/packages/dds/merge-tree/src/test/resetPendingSegmentsToOp.spec.ts +++ b/packages/dds/merge-tree/src/test/resetPendingSegmentsToOp.spec.ts @@ -164,7 +164,7 @@ describe("resetPendingSegmentsToOp", () => { assert(client.mergeTree.pendingSegments?.empty); opList.push({ - op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" }, undefined)!, + op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" })!, refSeq: client.getCurrentSeq(), }); applyOpList(client); @@ -176,7 +176,7 @@ describe("resetPendingSegmentsToOp", () => { assert(client.mergeTree.pendingSegments?.empty); opList.push({ - op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" }, undefined)!, + op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" })!, refSeq: client.getCurrentSeq(), }); opList.push({ @@ -196,7 +196,7 @@ describe("resetPendingSegmentsToOp", () => { it("nacked insertSegment and annotateRange", async () => { opList.push({ - op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" }, undefined)!, + op: client.annotateRangeLocal(0, client.getLength(), { foo: "bar" })!, refSeq: client.getCurrentSeq(), }); const oldops = opList; @@ -248,7 +248,7 @@ describe("resetPendingSegmentsToOp", () => { it("for text segments", () => { const insertOp = client.insertTextLocal(0, "abc", { prop1: "foo" }); assert(insertOp); - client.annotateRangeLocal(0, 3, { prop2: "bar" }, undefined); + client.annotateRangeLocal(0, 3, { prop2: "bar" }); const otherClient = new TestClient(); otherClient.startOrUpdateCollaboration("other user"); diff --git a/packages/dds/merge-tree/src/test/revertibles.spec.ts b/packages/dds/merge-tree/src/test/revertibles.spec.ts index 001e171227d4..1b063e55708b 100644 --- a/packages/dds/merge-tree/src/test/revertibles.spec.ts +++ b/packages/dds/merge-tree/src/test/revertibles.spec.ts @@ -131,12 +131,16 @@ describe("MergeTree.Revertibles", () => { // it should be fine to update these checks to allow a larger number of // calls assert( - linkCount <= length * 2, - `expected tracking group link to occur at most twice per segment. found ${linkCount}`, + linkCount <= length * 3, + `expected tracking group link to occur at most three times per segment. found ${linkCount} instead of ${ + length * 3 + }`, ); assert( - unlinkCount <= length, - `expected tracking group unlink to occur at most once per segment. found ${unlinkCount}`, + unlinkCount <= length * 2, + `expected tracking group unlink to occur at most twice per segment. found ${unlinkCount} instead of ${ + length * 2 + }`, ); } finally { unspy1(); @@ -276,12 +280,7 @@ describe("MergeTree.Revertibles", () => { clients.B.on("delta", (op, delta) => { appendToMergeTreeDeltaRevertibles(delta, clientB_Revertibles); }); - ops.push( - clients.B.makeOpMessage( - clients.B.annotateRangeLocal(0, 1, { test: 1 }, undefined), - ++seq, - ), - ); + ops.push(clients.B.makeOpMessage(clients.B.annotateRangeLocal(0, 1, { test: 1 }), ++seq)); ops.splice(0).forEach((op) => clients.all.forEach((c) => c.applyMsg(op))); logger.validate({ baseText: "123" }); @@ -386,12 +385,7 @@ describe("MergeTree.Revertibles", () => { clients.B.on("delta", deltaCallback); ops.push(clients.B.makeOpMessage(clients.B.removeRangeLocal(0, 2), ++seq)); - ops.push( - clients.B.makeOpMessage( - clients.B.annotateRangeLocal(0, 1, { test: 1 }, undefined), - ++seq, - ), - ); + ops.push(clients.B.makeOpMessage(clients.B.annotateRangeLocal(0, 1, { test: 1 }), ++seq)); ops.push(clients.B.makeOpMessage(clients.B.removeRangeLocal(0, 1), ++seq)); // revert to the original callback @@ -428,23 +422,13 @@ describe("MergeTree.Revertibles", () => { clientBDriver.submitOpCallback = (op) => ops.push(clients.B.makeOpMessage(op, ++seq)); clients.B.on("delta", deltaCallback); - ops.push( - clients.B.makeOpMessage( - clients.B.annotateRangeLocal(0, 4, { test: "B" }, undefined), - ++seq, - ), - ); + ops.push(clients.B.makeOpMessage(clients.B.annotateRangeLocal(0, 4, { test: "B" }), ++seq)); ops.push(clients.B.makeOpMessage(clients.B.removeRangeLocal(1, 2), ++seq)); // revert to the original callback clients.B.off("delta", deltaCallback); - ops.push( - clients.C.makeOpMessage( - clients.C.annotateRangeLocal(3, 4, { test: "C" }, undefined), - ++seq, - ), - ); + ops.push(clients.C.makeOpMessage(clients.C.annotateRangeLocal(3, 4, { test: "C" }), ++seq)); ops.splice(0).forEach((op) => clients.all.forEach((c) => c.applyMsg(op))); logger.validate({ baseText: "134" }); diff --git a/packages/dds/merge-tree/src/test/snapshot.spec.ts b/packages/dds/merge-tree/src/test/snapshot.spec.ts index 60c2a4c0abd4..1952c612d71c 100644 --- a/packages/dds/merge-tree/src/test/snapshot.spec.ts +++ b/packages/dds/merge-tree/src/test/snapshot.spec.ts @@ -93,6 +93,42 @@ function makeSnapshotSuite(options?: IMergeTreeOptions): void { await str.expect("0123"); }); + it("includes obliterates above the MSN of segments below the MSN", async () => { + str.append("0x", /* increaseMsn: */ true); + str.obliterateRange(1, 2, /* increaseMsn: */ false); + await str.expect("0"); + }); + + it("can insert segments after loading obliterated segment", async () => { + str.append("0x", /* increaseMsn: */ true); + str.obliterateRange(1, 2, /* increaseMsn: */ false); + await str.expect("0"); + str.append("1", /* increaseMsn: */ false); + await str.expect("01"); + }); + + it("can insert segments relative to obliterated segment", async () => { + str.append("0x", /* increaseMsn: */ false); + str.append("2", /* increaseMsn: */ false); + str.obliterateRange(1, 2, /* increaseMsn: */ false); + str.insert(1, "1", /* increaseMsn: */ false); + str.append("3", /* increaseMsn: */ false); + await str.expect("0123"); + }); + + it("can insert segments relative to obliterated segment loaded from snapshot", async () => { + str.append("0x", /* increaseMsn: */ false); + str.append("2", /* increaseMsn: */ false); + str.obliterateRange(1, 2, /* increaseMsn: */ false); + + // Note that calling str.expect() switches the underlying client to the one loaded from the snapshot. + await str.expect("02"); + + str.insert(1, "1", /* increaseMsn: */ false); + str.append("3", /* increaseMsn: */ false); + await str.expect("0123"); + }); + it("includes ACKed segments below MSN in body", async () => { for (let i = 0; i < SnapshotV1.chunkSize + 10; i++) { str.append(`${i % 10}`, /* increaseMsn: */ true); @@ -129,6 +165,7 @@ describe("snapshot", () => { describe("with attribution", () => { makeSnapshotSuite({ attribution: { track: true, policyFactory: createInsertOnlyAttributionPolicy }, + mergeTreeEnableObliterate: true, }); }); @@ -138,11 +175,15 @@ describe("snapshot", () => { track: true, policyFactory: createPropertyTrackingAttributionPolicyFactory("foo"), }, + mergeTreeEnableObliterate: true, }); }); describe("without attribution", () => { - makeSnapshotSuite({ attribution: { track: false } }); + makeSnapshotSuite({ + attribution: { track: false }, + mergeTreeEnableObliterate: true, + }); }); it("presence of attribution overrides merge-tree initialization value", async () => { diff --git a/packages/dds/merge-tree/src/test/snapshot.utils.ts b/packages/dds/merge-tree/src/test/snapshot.utils.ts index f5e3f4bc9809..63b2a5632c40 100644 --- a/packages/dds/merge-tree/src/test/snapshot.utils.ts +++ b/packages/dds/merge-tree/src/test/snapshot.utils.ts @@ -58,7 +58,7 @@ export class TestString { } public annotate(start: number, end: number, props: PropertySet, increaseMsn: boolean) { - this.queue(this.client.annotateRangeLocal(start, end, props, undefined)!, increaseMsn); + this.queue(this.client.annotateRangeLocal(start, end, props)!, increaseMsn); } public append(text: string, increaseMsn: boolean) { @@ -82,6 +82,10 @@ export class TestString { this.queue(this.client.removeRangeLocal(start, end)!, increaseMsn); } + public obliterateRange(start: number, end: number, increaseMsn: boolean) { + this.queue(this.client.obliterateRangeLocal(start, end)!, increaseMsn); + } + // Ensures the client's text matches the `expected` string and round-trips through a snapshot // into a new client. The current client is then replaced with the loaded client in the hope // that it will help detect corruption bugs as further ops are applied. diff --git a/packages/dds/merge-tree/src/test/snapshotlegacy.spec.ts b/packages/dds/merge-tree/src/test/snapshotlegacy.spec.ts index 14307bd2a627..be59e2c161f1 100644 --- a/packages/dds/merge-tree/src/test/snapshotlegacy.spec.ts +++ b/packages/dds/merge-tree/src/test/snapshotlegacy.spec.ts @@ -172,7 +172,7 @@ describe("snapshot", () => { ops.push( clients.B.makeOpMessage( - clients.B.annotateRangeLocal(0, 14, { foo: "bar" }, undefined), + clients.B.annotateRangeLocal(0, 14, { foo: "bar" }), /* seq */ 3, /* refSeq */ 2, ), diff --git a/packages/dds/merge-tree/src/test/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index 7636d023061e..f7d98c6c1937 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -17,7 +17,7 @@ import { MockStorage } from "@fluidframework/test-runtime-utils"; import { Trace } from "@fluid-internal/client-utils"; import { AttributionKey } from "@fluidframework/runtime-definitions"; import { Client } from "../client"; -import { List } from "../collections"; +import { DoublyLinkedList } from "../collections"; import { UnassignedSequenceNumber } from "../constants"; import { IMergeBlock, IMergeLeaf, ISegment, Marker, MaxNodesInBlock } from "../mergeTreeNodes"; import { createAnnotateRangeOp, createInsertSegmentOp, createRemoveRangeOp } from "../opBuilder"; @@ -31,7 +31,7 @@ import { IMergeTreeDeltaOpArgs } from "../mergeTreeDeltaCallback"; import { backwardExcursion, forwardExcursion, walkAllChildSegments } from "../mergeTreeNodeWalk"; import { DetachedReferencePosition, refHasTileLabel } from "../referencePositions"; import { MergeTreeRevertibleDriver } from "../revertibles"; -import { ReferencePosition } from ".."; +import { IMergeTreeOptions, ReferencePosition } from ".."; import { TestSerializer } from "./testSerializer"; import { nodeOrdinalsHaveIntegrity } from "./testUtils"; @@ -121,11 +121,10 @@ export class TestClient extends Client { ): Promise { const client2 = new TestClient(options, specToSeg); const { catchupOpsP } = await client2.load( - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions { logger: client2.logger, clientId: newLongClientId, - } as IFluidDataStoreRuntime, + } as any as IFluidDataStoreRuntime, storage, TestClient.serializer, ); @@ -135,11 +134,12 @@ export class TestClient extends Client { public readonly mergeTree: MergeTree; - public readonly checkQ: List = new List(); - protected readonly q: List = new List(); + public readonly checkQ: DoublyLinkedList = new DoublyLinkedList(); + protected readonly q: DoublyLinkedList = + new DoublyLinkedList(); private readonly textHelper: MergeTreeTextHelper; - constructor(options?: PropertySet, specToSeg = specToSegment) { + constructor(options?: IMergeTreeOptions & PropertySet, specToSeg = specToSegment) { super(specToSeg, createChildLogger({ namespace: "fluid:testClient" }), options); this.mergeTree = (this as Record<"_mergeTree", MergeTree>)._mergeTree; this.textHelper = new MergeTreeTextHelper(this.mergeTree); @@ -176,11 +176,7 @@ export class TestClient extends Client { overwrite?: boolean; opArgs: IMergeTreeDeltaOpArgs; }): void { - this.mergeTree.markRangeRemoved(start, end, refSeq, clientId, seq, overwrite, opArgs); - } - - public obliterateRangeLocal(start: number, end: number) { - return this.removeRangeLocal(start, end); + this.mergeTree.obliterateRange(start, end, refSeq, clientId, seq, overwrite, opArgs); } public getText(start?: number, end?: number): string { @@ -260,12 +256,7 @@ export class TestClient extends Client { longClientId: string, ) { this.applyMsg( - this.makeOpMessage( - createAnnotateRangeOp(start, end, props, undefined), - seq, - refSeq, - longClientId, - ), + this.makeOpMessage(createAnnotateRangeOp(start, end, props), seq, refSeq, longClientId), ); } @@ -428,7 +419,10 @@ export class TestClient extends Client { removedSeq !== undefined && (removedSeq !== UnassignedSequenceNumber || (localRemovedSeq !== undefined && localRemovedSeq <= localSeq)); - + const isMovedFromView = ({ movedSeq, localMovedSeq }: ISegment) => + movedSeq !== undefined && + (movedSeq !== UnassignedSequenceNumber || + (localMovedSeq !== undefined && localMovedSeq <= localSeq)); /* Walk the segments up to the current segment, and calculate its position taking into account local segments that were modified, @@ -445,15 +439,16 @@ export class TestClient extends Client { // // Note that all ACKed / remote ops are applied and we only need concern ourself with // determining if locally pending ops fall before/after the given 'localSeq'. - if (isInsertedInView(seg) && !isRemovedFromView(seg)) { + if (isInsertedInView(seg) && !isRemovedFromView(seg) && !isMovedFromView(seg)) { segmentPosition += seg.cachedLength; } return true; }); - assert( - fasterComputedPosition === segmentPosition, + assert.equal( + fasterComputedPosition, + segmentPosition, "Expected fast-path computation to match result from walk all segments", ); return segmentPosition; @@ -571,7 +566,7 @@ export const createRevertDriver = (client: TestClient): TestClientRevertibleDriv this.submitOpCallback?.(op); }, annotateRange(start: number, end: number, props: PropertySet) { - const op = client.annotateRangeLocal(start, end, props, undefined); + const op = client.annotateRangeLocal(start, end, props); this.submitOpCallback?.(op); }, insertFromSpec(pos: number, spec: IJSONSegment) { diff --git a/packages/dds/merge-tree/src/test/testClientLogger.ts b/packages/dds/merge-tree/src/test/testClientLogger.ts index b3f8c3ddb499..2e3c25924117 100644 --- a/packages/dds/merge-tree/src/test/testClientLogger.ts +++ b/packages/dds/merge-tree/src/test/testClientLogger.ts @@ -13,6 +13,7 @@ import { IMergeTreeDeltaOpArgs, MergeTreeMaintenanceType } from "../mergeTreeDel import { matchProperties, PropertySet } from "../properties"; import { depthFirstNodeWalk } from "../mergeTreeNodeWalk"; import { Marker, seqLTE, toRemovalInfo } from "../mergeTreeNodes"; +import { IMergeTreeOptions } from ".."; import { TestClient } from "./testClient"; function getOpString(msg: ISequencedDocumentMessage | undefined) { @@ -54,7 +55,7 @@ export function createClientsAtInitialState< >( opts: { initialState: string; - options?: PropertySet; + options?: IMergeTreeOptions & PropertySet; }, ...clientIds: TClientName[] ): Record & { all: TestClient[] } { diff --git a/packages/dds/merge-tree/src/test/testServer.ts b/packages/dds/merge-tree/src/test/testServer.ts index 9a158e61fedb..2663bc52cfec 100644 --- a/packages/dds/merge-tree/src/test/testServer.ts +++ b/packages/dds/merge-tree/src/test/testServer.ts @@ -4,20 +4,23 @@ */ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IIntegerRange } from "../base"; -import { Heap, RedBlackTree, Stack } from "../collections"; -import { - compareNumbers, - IncrementalExecOp, - IncrementalMapState, - ISegment, -} from "../mergeTreeNodes"; -import { ClientSeq, clientSeqComparer } from "../mergeTree"; +import { Heap, IComparer } from "@fluidframework/core-utils"; +import { RedBlackTree } from "../collections"; +import { compareNumbers } from "../mergeTreeNodes"; import { PropertySet } from "../properties"; -import { TextSegment } from "../textSegment"; import { MergeTreeTextHelper } from "../MergeTreeTextHelper"; import { TestClient } from "./testClient"; +interface ClientSeq { + refSeq: number; + clientId: string; +} + +const clientSeqComparer: IComparer = { + min: { refSeq: -1, clientId: "" }, + compare: (a, b) => a.refSeq - b.refSeq, +}; + /** * Server for tests. Simulates client communication by directing placing * messages in client queues. @@ -25,24 +28,14 @@ import { TestClient } from "./testClient"; export class TestServer extends TestClient { seq = 1; clients: TestClient[] = []; - private messageListeners: TestClient[] = []; // Listeners do not generate edits - clientSeqNumbers: Heap = new Heap([], clientSeqComparer); + clientSeqNumbers: Heap = new Heap(clientSeqComparer); upstreamMap: RedBlackTree = new RedBlackTree(compareNumbers); constructor(options?: PropertySet) { super(options); } - addUpstreamClients(upstreamClients: TestClient[]) { - // Assumes addClients already called - this.upstreamMap = new RedBlackTree(compareNumbers); - for (const upstreamClient of upstreamClients) { - this.clientSeqNumbers.add({ - refSeq: upstreamClient.getCurrentSeq(), - clientId: upstreamClient.longClientId ?? "", - }); - } - } + addClients(clients: TestClient[]) { - this.clientSeqNumbers = new Heap([], clientSeqComparer); + this.clientSeqNumbers = new Heap(clientSeqComparer); this.clients = clients; for (const client of clients) { this.clientSeqNumbers.add({ @@ -51,19 +44,17 @@ export class TestServer extends TestClient { }); } } - addListeners(listeners: TestClient[]) { - this.messageListeners = listeners; - } + applyMsg(msg: ISequencedDocumentMessage) { super.applyMsg(msg); if (TestClient.useCheckQ) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const clid = this.getShortClientId(msg.clientId as string); return checkTextMatchRelative(msg.referenceSequenceNumber, clid, this, msg); } else { return false; } } + // TODO: remove mappings when no longer needed using min seq // in upstream message transformUpstreamMessage(msg: ISequencedDocumentMessage) { @@ -79,8 +70,8 @@ export class TestServer extends TestClient { this.upstreamMap.put(msg.sequenceNumber, this.seq); msg.sequenceNumber = -1; } + copyMsg(msg: ISequencedDocumentMessage) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return { clientId: msg.clientId, clientSequenceNumber: msg.clientSequenceNumber, @@ -89,7 +80,7 @@ export class TestServer extends TestClient { referenceSequenceNumber: msg.referenceSequenceNumber, sequenceNumber: msg.sequenceNumber, type: msg.type, - } as ISequencedDocumentMessage; + } as any as ISequencedDocumentMessage; } private minSeq = 0; @@ -108,18 +99,19 @@ export class TestServer extends TestClient { return true; } if (this.clients) { - let minCli = this.clientSeqNumbers.peek(); + let minCli = this.clientSeqNumbers.peek()?.value; if ( minCli && minCli.clientId === msg.clientId && minCli.refSeq < msg.referenceSequenceNumber ) { - const cliSeq = this.clientSeqNumbers.get(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const cliSeq = this.clientSeqNumbers.get()!; const oldSeq = cliSeq.refSeq; cliSeq.refSeq = msg.referenceSequenceNumber; this.clientSeqNumbers.add(cliSeq); - minCli = this.clientSeqNumbers.peek(); - if (minCli.refSeq > oldSeq) { + minCli = this.clientSeqNumbers.peek()?.value; + if (minCli && minCli.refSeq > oldSeq) { msg.minimumSequenceNumber = minCli.refSeq; this.minSeq = minCli.refSeq; } @@ -127,11 +119,6 @@ export class TestServer extends TestClient { for (const client of this.clients) { client.enqueueMsg(msg); } - if (this.messageListeners) { - for (const listener of this.messageListeners) { - listener.enqueueMsg(this.copyMsg(msg)); - } - } } } else { break; @@ -140,48 +127,6 @@ export class TestServer extends TestClient { } return false; } - public incrementalGetText(start?: number, end?: number) { - const range: Partial = { start, end }; - if (range.start === undefined) { - range.start = 0; - } - if (range.end === undefined) { - range.end = this.getLength(); - } - const context = new TextSegment(""); - const stack = new Stack>(); - const initialState = new IncrementalMapState( - this.mergeTree.root, - { leaf: incrementalGatherText }, - 0, - this.getCurrentSeq(), - this.getClientId(), - context, - range.start, - range.end, - 0, - ); - stack.push(initialState); - - while (!stack.empty()) { - this.mergeTree.incrementalBlockMap(stack); - } - return context.text; - } -} - -function incrementalGatherText(segment: ISegment, state: IncrementalMapState) { - if (TextSegment.is(segment)) { - if (state.start <= 0 && state.end >= segment.text.length) { - state.context.text += segment.text; - } else { - state.context.text += - state.end >= segment.text.length - ? segment.text.substring(state.start) - : segment.text.substring(state.start, state.end); - } - } - state.op = IncrementalExecOp.Go; } /** @@ -199,8 +144,6 @@ export function checkTextMatchRelative( if (cliText === undefined || cliText !== serverText) { console.log(`mismatch `); console.log(msg); - // console.log(serverText); - // console.log(cliText); // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(server.mergeTree.toString()); // eslint-disable-next-line @typescript-eslint/no-base-to-string diff --git a/packages/dds/merge-tree/src/test/testUtils.ts b/packages/dds/merge-tree/src/test/testUtils.ts index f83a739fa84f..09d06a50dc47 100644 --- a/packages/dds/merge-tree/src/test/testUtils.ts +++ b/packages/dds/merge-tree/src/test/testUtils.ts @@ -12,6 +12,8 @@ import { ReferenceType } from "../ops"; import { PropertySet } from "../properties"; import { MergeTree } from "../mergeTree"; import { walkAllChildSegments } from "../mergeTreeNodeWalk"; +import { UnassignedSequenceNumber } from "../constants"; +import { LocalReferenceCollection } from "../localReference"; import { loadText } from "./text"; export function loadTextFromFile(filename: string, mergeTree: MergeTree, segLimit = 0) { @@ -179,22 +181,30 @@ function getPartialLengths( const isInserted = (segment: ISegment) => segment.seq === undefined || - (segment.seq !== -1 && segment.seq <= seq) || + (segment.seq !== UnassignedSequenceNumber && segment.seq <= seq) || (localSeq !== undefined && - segment.seq === -1 && + segment.seq === UnassignedSequenceNumber && segment.localSeq !== undefined && segment.localSeq <= localSeq); const isRemoved = (segment: ISegment) => segment.removedSeq !== undefined && ((localSeq !== undefined && - segment.removedSeq === -1 && + segment.removedSeq === UnassignedSequenceNumber && segment.localRemovedSeq !== undefined && segment.localRemovedSeq <= localSeq) || - (segment.removedSeq !== -1 && segment.removedSeq <= seq)); + (segment.removedSeq !== UnassignedSequenceNumber && segment.removedSeq <= seq)); + + const isMoved = (segment: ISegment) => + segment.movedSeq !== undefined && + ((localSeq !== undefined && + segment.movedSeq === UnassignedSequenceNumber && + segment.localMovedSeq !== undefined && + segment.localMovedSeq <= localSeq) || + (segment.movedSeq !== UnassignedSequenceNumber && segment.movedSeq <= seq)); walkAllChildSegments(mergeBlock, (segment) => { - if (isInserted(segment) && !isRemoved(segment)) { + if (isInserted(segment) && !isRemoved(segment) && !isMoved(segment)) { actualLen += segment.cachedLength; } return true; @@ -246,3 +256,14 @@ export function validatePartialLengths( assert.equal(actualLen, len); } } + +export function validateRefCount(collection?: LocalReferenceCollection) { + if (!collection) { + return; + } + + const expectedLength = Array.from(collection).length; + + // eslint-disable-next-line @typescript-eslint/dot-notation + assert.equal(collection["refCount"], expectedLength); +} diff --git a/packages/dds/merge-tree/src/test/tsconfig.json b/packages/dds/merge-tree/src/test/tsconfig.json index 24b6386c7684..d184cb2b21b2 100644 --- a/packages/dds/merge-tree/src/test/tsconfig.json +++ b/packages/dds/merge-tree/src/test/tsconfig.json @@ -18,5 +18,6 @@ // don't do. "declaration": true, "declarationMap": true, + "noImplicitAny": false, }, } diff --git a/packages/dds/merge-tree/src/test/tst.ts b/packages/dds/merge-tree/src/test/tst.ts deleted file mode 100644 index 4309dc0c0d1e..000000000000 --- a/packages/dds/merge-tree/src/test/tst.ts +++ /dev/null @@ -1,210 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export interface TSTResult { - key: string; - val: T; -} - -export interface TSTNode { - c: string; - left?: TSTNode; - mid?: TSTNode; - right?: TSTNode; - val?: T; -} - -interface TSTPrefix { - text: string; -} - -export interface ProxString { - text: string; - invDistance: number; - val: T; -} - -export class TST { - private n = 0; - private root: TSTNode | undefined; - - public size() { - return this.n; - } - - private contains(key: string) { - return this.get(key); - } - - public get(key: string) { - const x = this.nodeGet(this.root, key, 0); - if (x === undefined) { - return undefined; - } - return x.val; - } - - private nodeGet(x: TSTNode | undefined, key: string, d: number): TSTNode | undefined { - if (x === undefined) { - return undefined; - } - const c = key.charAt(d); - if (c < x.c) { - return this.nodeGet(x.left, key, d); - } else if (c > x.c) { - return this.nodeGet(x.right, key, d); - } else if (d < key.length - 1) { - return this.nodeGet(x.mid, key, d + 1); - } else { - return x; - } - } - - public put(key: string, val: T) { - if (!this.contains(key)) { - this.n++; - } - this.root = this.nodePut(this.root, key, val, 0); - } - - private nodePut(x: TSTNode | undefined, key: string, val: T, d: number) { - let _x = x; - const c = key.charAt(d); - if (_x === undefined) { - _x = { c }; - } - if (c < _x.c) { - _x.left = this.nodePut(_x.left, key, val, d); - } else if (c > _x.c) { - _x.right = this.nodePut(_x.right, key, val, d); - } else if (d < key.length - 1) { - _x.mid = this.nodePut(_x.mid, key, val, d + 1); - } else { - _x.val = val; - } - return _x; - } - - public neighbors(text: string, distance = 2) { - let q: ProxString[] = []; - this.nodeProximity(this.root, { text: "" }, 0, text, distance, q); - q = q.filter((value) => value.text.length > 0); - return q; - } - - public keysWithPrefix(text: string) { - const q: string[] = []; - const x = this.nodeGet(this.root, text, 0); - if (x === undefined) { - return q; - } - if (x.val !== undefined) { - q.push(text); - } - this.collect(x.mid, { text }, q); - return q; - } - - private collect(x: TSTNode | undefined, prefix: TSTPrefix, q: string[]) { - if (x === undefined) { - return; - } - this.collect(x.left, prefix, q); - if (x.val !== undefined) { - q.push(prefix.text + x.c); - } - this.collect(x.mid, { text: prefix.text + x.c }, q); - this.collect(x.right, prefix, q); - } - - private mapNode( - x: TSTNode | undefined, - prefix: TSTPrefix, - fn: (key: string, val: T) => void, - ) { - if (x === undefined) { - return; - } - const key = prefix.text + x.c; - this.mapNode(x.left, prefix, fn); - if (x.val) { - fn(key, x.val); - } - this.mapNode(x.mid, { text: key }, fn); - this.mapNode(x.right, prefix, fn); - } - - public map(fn: (key: string, val: T) => void) { - this.mapNode(this.root, { text: "" }, fn); - } - - public pairsWithPrefix(text: string) { - const q: TSTResult[] = []; - const x = this.nodeGet(this.root, text, 0); - if (x === undefined) { - return q; - } - if (x.val !== undefined) { - q.push({ key: text, val: x.val }); - } - this.collectPairs(x.mid, { text }, q); - return q; - } - - private collectPairs(x: TSTNode | undefined, prefix: TSTPrefix, q: TSTResult[]) { - if (x === undefined) { - return; - } - this.collectPairs(x.left, prefix, q); - if (x.val !== undefined) { - q.push({ key: prefix.text + x.c, val: x.val }); - } - this.collectPairs(x.mid, { text: prefix.text + x.c }, q); - this.collectPairs(x.right, prefix, q); - } - - private nodeProximity( - x: TSTNode | undefined, - prefix: TSTPrefix, - d: number, - pattern: string, - distance: number, - q: ProxString[], - ) { - if (x === undefined || distance < 0) { - return; - } - const c = pattern.charAt(d); - if (distance > 0 || c < x.c) { - this.nodeProximity(x.left, prefix, d, pattern, distance, q); - } - if (x.val !== undefined) { - const remD = distance - (pattern.length - d); - if (remD >= 0) { - let invD = distance; - if (c !== x.c) { - invD--; - } - q.push({ text: prefix.text + x.c, val: x.val, invDistance: invD }); - } - } - const recurD = d < pattern.length - 1 ? d + 1 : d; - if (c === x.c) { - this.nodeProximity(x.mid, { text: prefix.text + x.c }, recurD, pattern, distance, q); - } else { - this.nodeProximity( - x.mid, - { text: prefix.text + x.c }, - recurD, - pattern, - distance - 1, - q, - ); - } - if (distance > 0 || c > x.c) { - this.nodeProximity(x.right, prefix, d, pattern, distance, q); - } - } -} 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 bbd62210d56d..c383592d85f6 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -67,6 +67,7 @@ declare function get_current_ClassDeclaration_BaseSegment(): declare function use_old_ClassDeclaration_BaseSegment( use: TypeOnly): void; use_old_ClassDeclaration_BaseSegment( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_BaseSegment()); /* @@ -79,6 +80,7 @@ declare function get_old_ClassDeclaration_Client(): declare function use_current_ClassDeclaration_Client( use: TypeOnly): void; use_current_ClassDeclaration_Client( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_Client()); /* @@ -91,6 +93,7 @@ declare function get_current_ClassDeclaration_Client(): declare function use_old_ClassDeclaration_Client( use: TypeOnly): void; use_old_ClassDeclaration_Client( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_Client()); /* @@ -264,98 +267,50 @@ use_old_InterfaceDeclaration_IAttributionCollectionSpec( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_ICombiningOp": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_ICombiningOp": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_ICombiningOp(): - TypeOnly; -declare function use_current_InterfaceDeclaration_ICombiningOp( - use: TypeOnly): void; -use_current_InterfaceDeclaration_ICombiningOp( - get_old_InterfaceDeclaration_ICombiningOp()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_ICombiningOp": {"backCompat": false} +* "RemovedInterfaceDeclaration_ICombiningOp": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_ICombiningOp(): - TypeOnly; -declare function use_old_InterfaceDeclaration_ICombiningOp( - use: TypeOnly): void; -use_old_InterfaceDeclaration_ICombiningOp( - get_current_InterfaceDeclaration_ICombiningOp()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IConsensusInfo": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IConsensusInfo": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IConsensusInfo(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IConsensusInfo( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IConsensusInfo( - get_old_InterfaceDeclaration_IConsensusInfo()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IConsensusInfo": {"backCompat": false} +* "RemovedInterfaceDeclaration_IConsensusInfo": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IConsensusInfo(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IConsensusInfo( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IConsensusInfo( - get_current_InterfaceDeclaration_IConsensusInfo()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IConsensusValue": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IConsensusValue": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IConsensusValue(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IConsensusValue( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IConsensusValue( - get_old_InterfaceDeclaration_IConsensusValue()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IConsensusValue": {"backCompat": false} +* "RemovedInterfaceDeclaration_IConsensusValue": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IConsensusValue(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IConsensusValue( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IConsensusValue( - get_current_InterfaceDeclaration_IConsensusValue()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IIntegerRange": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IIntegerRange": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IIntegerRange(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IIntegerRange( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IIntegerRange( - get_old_InterfaceDeclaration_IIntegerRange()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IIntegerRange": {"backCompat": false} +* "RemovedInterfaceDeclaration_IIntegerRange": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IIntegerRange(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IIntegerRange( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IIntegerRange( - get_current_InterfaceDeclaration_IIntegerRange()); /* * Validate forward compat by using old type in place of current type @@ -456,26 +411,14 @@ use_old_InterfaceDeclaration_IMarkerDef( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IMarkerModifiedAction": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IMarkerModifiedAction": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IMarkerModifiedAction(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IMarkerModifiedAction( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IMarkerModifiedAction( - get_old_InterfaceDeclaration_IMarkerModifiedAction()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IMarkerModifiedAction": {"backCompat": false} +* "RemovedInterfaceDeclaration_IMarkerModifiedAction": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IMarkerModifiedAction(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IMarkerModifiedAction( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IMarkerModifiedAction( - get_current_InterfaceDeclaration_IMarkerModifiedAction()); /* * Validate forward compat by using old type in place of current type @@ -1056,26 +999,14 @@ use_old_InterfaceDeclaration_KeyComparer( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_LocalClientId": {"forwardCompat": false} +* "RemovedVariableDeclaration_LocalClientId": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_LocalClientId(): - TypeOnly; -declare function use_current_VariableDeclaration_LocalClientId( - use: TypeOnly): void; -use_current_VariableDeclaration_LocalClientId( - get_old_VariableDeclaration_LocalClientId()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_LocalClientId": {"backCompat": false} +* "RemovedVariableDeclaration_LocalClientId": {"backCompat": false} */ -declare function get_current_VariableDeclaration_LocalClientId(): - TypeOnly; -declare function use_old_VariableDeclaration_LocalClientId( - use: TypeOnly): void; -use_old_VariableDeclaration_LocalClientId( - get_current_VariableDeclaration_LocalClientId()); /* * Validate forward compat by using old type in place of current type @@ -1099,6 +1030,7 @@ declare function get_current_ClassDeclaration_LocalReferenceCollection(): declare function use_old_ClassDeclaration_LocalReferenceCollection( use: TypeOnly): void; use_old_ClassDeclaration_LocalReferenceCollection( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_LocalReferenceCollection()); /* @@ -1171,6 +1103,7 @@ declare function get_current_ClassDeclaration_Marker(): declare function use_old_ClassDeclaration_Marker( use: TypeOnly): void; use_old_ClassDeclaration_Marker( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_Marker()); /* @@ -1200,26 +1133,14 @@ use_old_ClassDeclaration_MergeNode( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_MergeTreeDeltaCallback": {"forwardCompat": false} +* "RemovedTypeAliasDeclaration_MergeTreeDeltaCallback": {"forwardCompat": false} */ -declare function get_old_TypeAliasDeclaration_MergeTreeDeltaCallback(): - TypeOnly; -declare function use_current_TypeAliasDeclaration_MergeTreeDeltaCallback( - use: TypeOnly): void; -use_current_TypeAliasDeclaration_MergeTreeDeltaCallback( - get_old_TypeAliasDeclaration_MergeTreeDeltaCallback()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_MergeTreeDeltaCallback": {"backCompat": false} +* "RemovedTypeAliasDeclaration_MergeTreeDeltaCallback": {"backCompat": false} */ -declare function get_current_TypeAliasDeclaration_MergeTreeDeltaCallback(): - TypeOnly; -declare function use_old_TypeAliasDeclaration_MergeTreeDeltaCallback( - use: TypeOnly): void; -use_old_TypeAliasDeclaration_MergeTreeDeltaCallback( - get_current_TypeAliasDeclaration_MergeTreeDeltaCallback()); /* * Validate forward compat by using old type in place of current type @@ -1303,6 +1224,7 @@ declare function get_old_VariableDeclaration_MergeTreeDeltaType(): declare function use_current_VariableDeclaration_MergeTreeDeltaType( use: TypeOnly): void; use_current_VariableDeclaration_MergeTreeDeltaType( + // @ts-expect-error compatibility expected to be broken get_old_VariableDeclaration_MergeTreeDeltaType()); /* @@ -1344,26 +1266,14 @@ use_old_TypeAliasDeclaration_MergeTreeDeltaType( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_MergeTreeMaintenanceCallback": {"forwardCompat": false} +* "RemovedTypeAliasDeclaration_MergeTreeMaintenanceCallback": {"forwardCompat": false} */ -declare function get_old_TypeAliasDeclaration_MergeTreeMaintenanceCallback(): - TypeOnly; -declare function use_current_TypeAliasDeclaration_MergeTreeMaintenanceCallback( - use: TypeOnly): void; -use_current_TypeAliasDeclaration_MergeTreeMaintenanceCallback( - get_old_TypeAliasDeclaration_MergeTreeMaintenanceCallback()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_MergeTreeMaintenanceCallback": {"backCompat": false} +* "RemovedTypeAliasDeclaration_MergeTreeMaintenanceCallback": {"backCompat": false} */ -declare function get_current_TypeAliasDeclaration_MergeTreeMaintenanceCallback(): - TypeOnly; -declare function use_old_TypeAliasDeclaration_MergeTreeMaintenanceCallback( - use: TypeOnly): void; -use_old_TypeAliasDeclaration_MergeTreeMaintenanceCallback( - get_current_TypeAliasDeclaration_MergeTreeMaintenanceCallback()); /* * Validate forward compat by using old type in place of current type @@ -1440,26 +1350,14 @@ use_old_InterfaceDeclaration_MergeTreeRevertibleDriver( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_NonCollabClient": {"forwardCompat": false} +* "RemovedVariableDeclaration_NonCollabClient": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_NonCollabClient(): - TypeOnly; -declare function use_current_VariableDeclaration_NonCollabClient( - use: TypeOnly): void; -use_current_VariableDeclaration_NonCollabClient( - get_old_VariableDeclaration_NonCollabClient()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_NonCollabClient": {"backCompat": false} +* "RemovedVariableDeclaration_NonCollabClient": {"backCompat": false} */ -declare function get_current_VariableDeclaration_NonCollabClient(): - TypeOnly; -declare function use_old_VariableDeclaration_NonCollabClient( - use: TypeOnly): void; -use_old_VariableDeclaration_NonCollabClient( - get_current_VariableDeclaration_NonCollabClient()); /* * Validate forward compat by using old type in place of current type @@ -1704,26 +1602,14 @@ use_old_InterfaceDeclaration_RBNodeActions( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_RangeStackMap": {"forwardCompat": false} +* "RemovedTypeAliasDeclaration_RangeStackMap": {"forwardCompat": false} */ -declare function get_old_TypeAliasDeclaration_RangeStackMap(): - TypeOnly; -declare function use_current_TypeAliasDeclaration_RangeStackMap( - use: TypeOnly): void; -use_current_TypeAliasDeclaration_RangeStackMap( - get_old_TypeAliasDeclaration_RangeStackMap()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "TypeAliasDeclaration_RangeStackMap": {"backCompat": false} +* "RemovedTypeAliasDeclaration_RangeStackMap": {"backCompat": false} */ -declare function get_current_TypeAliasDeclaration_RangeStackMap(): - TypeOnly; -declare function use_old_TypeAliasDeclaration_RangeStackMap( - use: TypeOnly): void; -use_old_TypeAliasDeclaration_RangeStackMap( - get_current_TypeAliasDeclaration_RangeStackMap()); /* * Validate forward compat by using old type in place of current type @@ -1800,26 +1686,14 @@ use_old_EnumDeclaration_ReferenceType( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_SegmentAccumulator": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_SegmentAccumulator": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_SegmentAccumulator(): - TypeOnly; -declare function use_current_InterfaceDeclaration_SegmentAccumulator( - use: TypeOnly): void; -use_current_InterfaceDeclaration_SegmentAccumulator( - get_old_InterfaceDeclaration_SegmentAccumulator()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_SegmentAccumulator": {"backCompat": false} +* "RemovedInterfaceDeclaration_SegmentAccumulator": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_SegmentAccumulator(): - TypeOnly; -declare function use_old_InterfaceDeclaration_SegmentAccumulator( - use: TypeOnly): void; -use_old_InterfaceDeclaration_SegmentAccumulator( - get_current_InterfaceDeclaration_SegmentAccumulator()); /* * Validate forward compat by using old type in place of current type @@ -1843,6 +1717,7 @@ declare function get_current_InterfaceDeclaration_SegmentGroup(): declare function use_old_InterfaceDeclaration_SegmentGroup( use: TypeOnly): void; use_old_InterfaceDeclaration_SegmentGroup( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_SegmentGroup()); /* @@ -2064,26 +1939,14 @@ use_old_ClassDeclaration_SortedSet( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_Stack": {"forwardCompat": false} +* "RemovedClassDeclaration_Stack": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_Stack(): - TypeOnly>; -declare function use_current_ClassDeclaration_Stack( - use: TypeOnly>): void; -use_current_ClassDeclaration_Stack( - get_old_ClassDeclaration_Stack()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_Stack": {"backCompat": false} +* "RemovedClassDeclaration_Stack": {"backCompat": false} */ -declare function get_current_ClassDeclaration_Stack(): - TypeOnly>; -declare function use_old_ClassDeclaration_Stack( - use: TypeOnly>): void; -use_old_ClassDeclaration_Stack( - get_current_ClassDeclaration_Stack()); /* * Validate forward compat by using old type in place of current type @@ -2107,6 +1970,7 @@ declare function get_current_ClassDeclaration_TextSegment(): declare function use_old_ClassDeclaration_TextSegment( use: TypeOnly): void; use_old_ClassDeclaration_TextSegment( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_TextSegment()); /* @@ -2184,26 +2048,14 @@ use_old_ClassDeclaration_TrackingGroupCollection( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_TreeMaintenanceSequenceNumber": {"forwardCompat": false} +* "RemovedVariableDeclaration_TreeMaintenanceSequenceNumber": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_TreeMaintenanceSequenceNumber(): - TypeOnly; -declare function use_current_VariableDeclaration_TreeMaintenanceSequenceNumber( - use: TypeOnly): void; -use_current_VariableDeclaration_TreeMaintenanceSequenceNumber( - get_old_VariableDeclaration_TreeMaintenanceSequenceNumber()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_TreeMaintenanceSequenceNumber": {"backCompat": false} +* "RemovedVariableDeclaration_TreeMaintenanceSequenceNumber": {"backCompat": false} */ -declare function get_current_VariableDeclaration_TreeMaintenanceSequenceNumber(): - TypeOnly; -declare function use_old_VariableDeclaration_TreeMaintenanceSequenceNumber( - use: TypeOnly): void; -use_old_VariableDeclaration_TreeMaintenanceSequenceNumber( - get_current_VariableDeclaration_TreeMaintenanceSequenceNumber()); /* * Validate forward compat by using old type in place of current type @@ -2304,74 +2156,38 @@ use_old_FunctionDeclaration_appendToMergeTreeDeltaRevertibles( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_clone": {"forwardCompat": false} +* "RemovedFunctionDeclaration_clone": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_clone(): - TypeOnly; -declare function use_current_FunctionDeclaration_clone( - use: TypeOnly): void; -use_current_FunctionDeclaration_clone( - get_old_FunctionDeclaration_clone()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_clone": {"backCompat": false} +* "RemovedFunctionDeclaration_clone": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_clone(): - TypeOnly; -declare function use_old_FunctionDeclaration_clone( - use: TypeOnly): void; -use_old_FunctionDeclaration_clone( - get_current_FunctionDeclaration_clone()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_combine": {"forwardCompat": false} +* "RemovedFunctionDeclaration_combine": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_combine(): - TypeOnly; -declare function use_current_FunctionDeclaration_combine( - use: TypeOnly): void; -use_current_FunctionDeclaration_combine( - get_old_FunctionDeclaration_combine()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_combine": {"backCompat": false} +* "RemovedFunctionDeclaration_combine": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_combine(): - TypeOnly; -declare function use_old_FunctionDeclaration_combine( - use: TypeOnly): void; -use_old_FunctionDeclaration_combine( - get_current_FunctionDeclaration_combine()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_compareNumbers": {"forwardCompat": false} +* "RemovedVariableDeclaration_compareNumbers": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_compareNumbers(): - TypeOnly; -declare function use_current_VariableDeclaration_compareNumbers( - use: TypeOnly): void; -use_current_VariableDeclaration_compareNumbers( - get_old_VariableDeclaration_compareNumbers()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_compareNumbers": {"backCompat": false} +* "RemovedVariableDeclaration_compareNumbers": {"backCompat": false} */ -declare function get_current_VariableDeclaration_compareNumbers(): - TypeOnly; -declare function use_old_VariableDeclaration_compareNumbers( - use: TypeOnly): void; -use_old_VariableDeclaration_compareNumbers( - get_current_VariableDeclaration_compareNumbers()); /* * Validate forward compat by using old type in place of current type @@ -2400,50 +2216,26 @@ use_old_FunctionDeclaration_compareReferencePositions( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_compareStrings": {"forwardCompat": false} +* "RemovedVariableDeclaration_compareStrings": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_compareStrings(): - TypeOnly; -declare function use_current_VariableDeclaration_compareStrings( - use: TypeOnly): void; -use_current_VariableDeclaration_compareStrings( - get_old_VariableDeclaration_compareStrings()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_compareStrings": {"backCompat": false} +* "RemovedVariableDeclaration_compareStrings": {"backCompat": false} */ -declare function get_current_VariableDeclaration_compareStrings(): - TypeOnly; -declare function use_old_VariableDeclaration_compareStrings( - use: TypeOnly): void; -use_old_VariableDeclaration_compareStrings( - get_current_VariableDeclaration_compareStrings()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_createAnnotateMarkerOp": {"forwardCompat": false} +* "RemovedFunctionDeclaration_createAnnotateMarkerOp": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_createAnnotateMarkerOp(): - TypeOnly; -declare function use_current_FunctionDeclaration_createAnnotateMarkerOp( - use: TypeOnly): void; -use_current_FunctionDeclaration_createAnnotateMarkerOp( - get_old_FunctionDeclaration_createAnnotateMarkerOp()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_createAnnotateMarkerOp": {"backCompat": false} +* "RemovedFunctionDeclaration_createAnnotateMarkerOp": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_createAnnotateMarkerOp(): - TypeOnly; -declare function use_old_FunctionDeclaration_createAnnotateMarkerOp( - use: TypeOnly): void; -use_old_FunctionDeclaration_createAnnotateMarkerOp( - get_current_FunctionDeclaration_createAnnotateMarkerOp()); /* * Validate forward compat by using old type in place of current type @@ -2688,50 +2480,26 @@ use_old_FunctionDeclaration_discardMergeTreeDeltaRevertible( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_extend": {"forwardCompat": false} +* "RemovedFunctionDeclaration_extend": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_extend(): - TypeOnly; -declare function use_current_FunctionDeclaration_extend( - use: TypeOnly): void; -use_current_FunctionDeclaration_extend( - get_old_FunctionDeclaration_extend()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_extend": {"backCompat": false} +* "RemovedFunctionDeclaration_extend": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_extend(): - TypeOnly; -declare function use_old_FunctionDeclaration_extend( - use: TypeOnly): void; -use_old_FunctionDeclaration_extend( - get_current_FunctionDeclaration_extend()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_extendIfUndefined": {"forwardCompat": false} +* "RemovedFunctionDeclaration_extendIfUndefined": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_extendIfUndefined(): - TypeOnly; -declare function use_current_FunctionDeclaration_extendIfUndefined( - use: TypeOnly): void; -use_current_FunctionDeclaration_extendIfUndefined( - get_old_FunctionDeclaration_extendIfUndefined()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_extendIfUndefined": {"backCompat": false} +* "RemovedFunctionDeclaration_extendIfUndefined": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_extendIfUndefined(): - TypeOnly; -declare function use_old_FunctionDeclaration_extendIfUndefined( - use: TypeOnly): void; -use_old_FunctionDeclaration_extendIfUndefined( - get_current_FunctionDeclaration_extendIfUndefined()); /* * Validate forward compat by using old type in place of current type @@ -2760,26 +2528,14 @@ use_old_FunctionDeclaration_getSlideToSegoff( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_internedSpaces": {"forwardCompat": false} +* "RemovedFunctionDeclaration_internedSpaces": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_internedSpaces(): - TypeOnly; -declare function use_current_FunctionDeclaration_internedSpaces( - use: TypeOnly): void; -use_current_FunctionDeclaration_internedSpaces( - get_old_FunctionDeclaration_internedSpaces()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_internedSpaces": {"backCompat": false} +* "RemovedFunctionDeclaration_internedSpaces": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_internedSpaces(): - TypeOnly; -declare function use_old_FunctionDeclaration_internedSpaces( - use: TypeOnly): void; -use_old_FunctionDeclaration_internedSpaces( - get_current_FunctionDeclaration_internedSpaces()); /* * Validate forward compat by using old type in place of current type @@ -2880,26 +2636,14 @@ use_old_FunctionDeclaration_minReferencePosition( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_refGetRangeLabels": {"forwardCompat": false} +* "RemovedVariableDeclaration_refGetRangeLabels": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_refGetRangeLabels(): - TypeOnly; -declare function use_current_VariableDeclaration_refGetRangeLabels( - use: TypeOnly): void; -use_current_VariableDeclaration_refGetRangeLabels( - get_old_VariableDeclaration_refGetRangeLabels()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_refGetRangeLabels": {"backCompat": false} +* "RemovedVariableDeclaration_refGetRangeLabels": {"backCompat": false} */ -declare function get_current_VariableDeclaration_refGetRangeLabels(): - TypeOnly; -declare function use_old_VariableDeclaration_refGetRangeLabels( - use: TypeOnly): void; -use_old_VariableDeclaration_refGetRangeLabels( - get_current_VariableDeclaration_refGetRangeLabels()); /* * Validate forward compat by using old type in place of current type @@ -2928,50 +2672,26 @@ use_old_VariableDeclaration_refGetTileLabels( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_refHasRangeLabel": {"forwardCompat": false} +* "RemovedFunctionDeclaration_refHasRangeLabel": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_refHasRangeLabel(): - TypeOnly; -declare function use_current_FunctionDeclaration_refHasRangeLabel( - use: TypeOnly): void; -use_current_FunctionDeclaration_refHasRangeLabel( - get_old_FunctionDeclaration_refHasRangeLabel()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_refHasRangeLabel": {"backCompat": false} +* "RemovedFunctionDeclaration_refHasRangeLabel": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_refHasRangeLabel(): - TypeOnly; -declare function use_old_FunctionDeclaration_refHasRangeLabel( - use: TypeOnly): void; -use_old_FunctionDeclaration_refHasRangeLabel( - get_current_FunctionDeclaration_refHasRangeLabel()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_refHasRangeLabels": {"forwardCompat": false} +* "RemovedFunctionDeclaration_refHasRangeLabels": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_refHasRangeLabels(): - TypeOnly; -declare function use_current_FunctionDeclaration_refHasRangeLabels( - use: TypeOnly): void; -use_current_FunctionDeclaration_refHasRangeLabels( - get_old_FunctionDeclaration_refHasRangeLabels()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_refHasRangeLabels": {"backCompat": false} +* "RemovedFunctionDeclaration_refHasRangeLabels": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_refHasRangeLabels(): - TypeOnly; -declare function use_old_FunctionDeclaration_refHasRangeLabels( - use: TypeOnly): void; -use_old_FunctionDeclaration_refHasRangeLabels( - get_current_FunctionDeclaration_refHasRangeLabels()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/dds/merge-tree/src/test/wordUnitTests.ts b/packages/dds/merge-tree/src/test/wordUnitTests.ts index 216411d7d252..1ae750d53af3 100644 --- a/packages/dds/merge-tree/src/test/wordUnitTests.ts +++ b/packages/dds/merge-tree/src/test/wordUnitTests.ts @@ -152,17 +152,10 @@ function measureFetch(startFile: string, withBookmarks = false) { console.log(`inserting ${bookmarkCount} refs into text`); } const reps = 20; - let clockStart = clock(); + const clockStart = clock(); let count = 0; for (let i = 0; i < reps; i++) { for (let pos = 0; pos < client.getLength(); ) { - // let prevPG = client.findTile(pos, "pg"); - // let caBegin: number; - // if (prevPG) { - // caBegin = prevPG.pos; - // } else { - // caBegin = 0; - // } // curPG.pos is ca end const curPG = client.findTile(pos, "pg", false)!; const properties = curPG.tile.properties!; @@ -174,19 +167,12 @@ function measureFetch(startFile: string, withBookmarks = false) { count++; } } - let et = elapsedMicroseconds(clockStart); + const et = elapsedMicroseconds(clockStart); console.log( `fetch of ${count / reps} runs over ${client.getLength()} total chars took ${( et / count ).toFixed(1)} microseconds per run`, ); - // Bonus: measure clone - clockStart = clock(); - for (let i = 0; i < reps; i++) { - client.mergeTree.clone(); - } - et = elapsedMicroseconds(clockStart); - console.log(`naive clone took ${(et / (1000 * reps)).toFixed(1)} milliseconds`); } const baseDir = "../../src/test/literature"; diff --git a/packages/dds/merge-tree/src/textSegment.ts b/packages/dds/merge-tree/src/textSegment.ts index 3102e2ccfed7..8e8a4ca7c47e 100644 --- a/packages/dds/merge-tree/src/textSegment.ts +++ b/packages/dds/merge-tree/src/textSegment.ts @@ -20,7 +20,7 @@ import { PropertySet } from "./properties"; export const TextSegmentGranularity = 256; /** - * @internal + * @alpha */ export interface IJSONTextSegment extends IJSONSegment { text: string; @@ -31,6 +31,7 @@ export interface IJSONTextSegment extends IJSONSegment { */ export class TextSegment extends BaseSegment { public static readonly type = "TextSegment"; + public readonly type = TextSegment.type; public static is(segment: ISegment): segment is TextSegment { return segment.type === TextSegment.type; @@ -54,14 +55,12 @@ export class TextSegment extends BaseSegment { return undefined; } - public readonly type = TextSegment.type; - constructor(public text: string) { super(); this.cachedLength = text.length; } - public toJSONObject() { + public toJSONObject(): IJSONTextSegment | string { // To reduce snapshot/ops size, we serialize a TextSegment as a plain 'string' if it is // not annotated. return this.properties ? { text: this.text, props: this.properties } : this.text; @@ -93,22 +92,6 @@ export class TextSegment extends BaseSegment { this.text += segment.text; } - // TODO: retain removed text for undo - // returns true if entire string removed - public removeRange(start: number, end: number) { - let remnantString = ""; - const len = this.text.length; - if (start > 0) { - remnantString += this.text.substring(0, start); - } - if (end < len) { - remnantString += this.text.substring(end); - } - this.text = remnantString; - this.cachedLength = remnantString.length; - return remnantString.length === 0; - } - protected createSplitSegmentAt(pos: number) { if (pos > 0) { const remainingText = this.text.substring(pos); diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 53921020169d..6abe20e22968 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { UnassignedSequenceNumber } from "./constants"; @@ -13,8 +12,10 @@ import { IMergeBlock, IMergeNode, ISegment, + Marker, MaxNodesInBlock, seqLTE, + toMoveInfo, toRemovalInfo, } from "./mergeTreeNodes"; import { matchProperties } from "./properties"; @@ -33,11 +34,11 @@ export function zamboniSegments( } for (let i = 0; i < zamboniSegmentsMaxCount; i++) { - let segmentToScour = mergeTree.segmentsToScour.peek(); + let segmentToScour = mergeTree.segmentsToScour.peek()?.value; if (!segmentToScour || segmentToScour.maxSeq > mergeTree.collabWindow.minSeq) { break; } - segmentToScour = mergeTree.segmentsToScour.get(); + segmentToScour = mergeTree.segmentsToScour.get()!; // Only skip scouring if needs scour is explicitly false, not true or undefined if (segmentToScour.segment!.parent && segmentToScour.segment!.parent.needsScour !== false) { const block = segmentToScour.segment!.parent; @@ -75,8 +76,7 @@ export function packParent(parent: IMergeBlock, mergeTree: MergeTree) { const holdNodes: IMergeNode[] = []; for (childIndex = 0; childIndex < parent.childCount; childIndex++) { // Debug assert not isLeaf() - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - childBlock = children[childIndex]; + childBlock = children[childIndex] as IMergeBlock; scourNode(childBlock, holdNodes, mergeTree); // Will replace this block with a packed block childBlock.parent = undefined; @@ -141,11 +141,13 @@ function scourNode(node: IMergeBlock, holdNodes: IMergeNode[], mergeTree: MergeT const segment = childNode; const removalInfo = toRemovalInfo(segment); - if (removalInfo !== undefined) { + const moveInfo = toMoveInfo(segment); + if (removalInfo !== undefined || moveInfo !== undefined) { // If the segment's removal is below the MSN and it's not being held onto by a tracking group, // it can be unlinked (i.e. removed from the merge-tree) if ( - seqLTE(removalInfo.removedSeq, mergeTree.collabWindow.minSeq) && + ((!!removalInfo && seqLTE(removalInfo.removedSeq, mergeTree.collabWindow.minSeq)) || + (!!moveInfo && seqLTE(moveInfo.movedSeq, mergeTree.collabWindow.minSeq))) && segment.trackingCollection.empty ) { mergeTree.mergeTreeMaintenanceCallback?.( @@ -157,6 +159,10 @@ function scourNode(node: IMergeBlock, holdNodes: IMergeNode[], mergeTree: MergeT ); segment.parent = undefined; + + if (Marker.is(segment)) { + mergeTree.unlinkMarker(segment); + } } else { holdNodes.push(segment); } diff --git a/packages/dds/merge-tree/tsconfig.json b/packages/dds/merge-tree/tsconfig.json index f1276d46b301..b3a6746c0a81 100644 --- a/packages/dds/merge-tree/tsconfig.json +++ b/packages/dds/merge-tree/tsconfig.json @@ -9,5 +9,6 @@ "rootDir": "./src", "outDir": "./dist", "preserveConstEnums": true, + "noImplicitAny": true, }, } diff --git a/packages/dds/ordered-collection/package.json b/packages/dds/ordered-collection/package.json index e0d8fc7f93aa..766bde3d000c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Consensus Collection", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/ordered-collection/src/packageVersion.ts b/packages/dds/ordered-collection/src/packageVersion.ts index ac3810324919..a7299979e6a1 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/pact-map/package.json b/packages/dds/pact-map/package.json index 22a8ba0579ec..3c99ea0d16ac 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed data structure for key-value pairs using pact consensus", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/pact-map/src/packageVersion.ts b/packages/dds/pact-map/src/packageVersion.ts index ebdd33515dc8..c3eaa7d09683 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/register-collection/package.json b/packages/dds/register-collection/package.json index 32128c4627f5..31b7afe916d7 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Consensus Register", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/register-collection/src/packageVersion.ts b/packages/dds/register-collection/src/packageVersion.ts index f7a9554622b5..5bb531c8a079 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index b09033468039..1e1fd86be6a5 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -11,7 +11,6 @@ import { IChannelAttributes } from '@fluidframework/datastore-definitions'; import { IChannelFactory } from '@fluidframework/datastore-definitions'; import { IChannelServices } from '@fluidframework/datastore-definitions'; import { IChannelStorageService } from '@fluidframework/datastore-definitions'; -import { ICombiningOp } from '@fluidframework/merge-tree'; import { IEvent } from '@fluidframework/core-interfaces'; import { IEventThisPlaceHolder } from '@fluidframework/core-interfaces'; import { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions'; @@ -20,10 +19,7 @@ import { IJSONSegment } from '@fluidframework/merge-tree'; import { IMergeTreeDeltaCallbackArgs } from '@fluidframework/merge-tree'; import { IMergeTreeDeltaOpArgs } from '@fluidframework/merge-tree'; import { IMergeTreeGroupMsg } from '@fluidframework/merge-tree'; -import { IMergeTreeInsertMsg } from '@fluidframework/merge-tree'; import { IMergeTreeMaintenanceCallbackArgs } from '@fluidframework/merge-tree'; -import { IMergeTreeOp } from '@fluidframework/merge-tree'; -import { IMergeTreeRemoveMsg } from '@fluidframework/merge-tree'; import { IRelativePosition } from '@fluidframework/merge-tree'; import { ISegment } from '@fluidframework/merge-tree'; import { ISegmentAction } from '@fluidframework/merge-tree'; @@ -32,22 +28,27 @@ import { ISharedObjectEvents } from '@fluidframework/shared-object-base'; import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions'; import { ITelemetryContext } from '@fluidframework/runtime-definitions'; import { LocalReferencePosition } from '@fluidframework/merge-tree'; +import { MapLike } from '@fluidframework/merge-tree'; import { Marker } from '@fluidframework/merge-tree'; import { MergeTreeDeltaOperationType } from '@fluidframework/merge-tree'; import { MergeTreeDeltaOperationTypes } from '@fluidframework/merge-tree'; import { MergeTreeDeltaRevertible } from '@fluidframework/merge-tree'; +import { MergeTreeDeltaType } from '@fluidframework/merge-tree'; import { MergeTreeMaintenanceType } from '@fluidframework/merge-tree'; import { MergeTreeRevertibleDriver } from '@fluidframework/merge-tree'; import { PropertiesManager } from '@fluidframework/merge-tree'; import { PropertySet } from '@fluidframework/merge-tree'; -import { RangeStackMap } from '@fluidframework/merge-tree'; import { ReferencePosition } from '@fluidframework/merge-tree'; import { ReferenceType } from '@fluidframework/merge-tree'; +import { reservedMarkerIdKey } from '@fluidframework/merge-tree'; +import { reservedRangeLabelsKey } from '@fluidframework/merge-tree'; +import { reservedTileLabelsKey } from '@fluidframework/merge-tree'; import { Serializable } from '@fluidframework/datastore-definitions'; import { SharedObject } from '@fluidframework/shared-object-base'; import { SlidingPreference } from '@fluidframework/merge-tree'; import { SummarySerializer } from '@fluidframework/shared-object-base'; import { TextSegment } from '@fluidframework/merge-tree'; +import { TrackingGroup } from '@fluidframework/merge-tree'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; // @internal @@ -65,6 +66,8 @@ export function appendIntervalPropertyChangedToRevertibles(interval: SequenceInt // @internal export function appendSharedStringDeltaToRevertibles(string: SharedString, delta: SequenceDeltaEvent, revertibles: SharedStringRevertible[]): void; +export { BaseSegment } + // @internal (undocumented) export function createEndpointIndex(sharedString: SharedString): IEndpointIndex; @@ -147,7 +150,7 @@ export interface IIntervalCollection ex readonly attached: boolean; attachIndex(index: IntervalIndex): void; change(id: string, start: SequencePlace, end: SequencePlace): TInterval | undefined; - changeProperties(id: string, props: PropertySet): any; + changeProperties(id: string, props: PropertySet): void; // (undocumented) CreateBackwardIteratorWithEndPosition(endPosition: number): Iterator; // (undocumented) @@ -172,9 +175,9 @@ export interface IIntervalCollection ex // @alpha export interface IIntervalCollectionEvent extends IEvent { - (event: "changeInterval", listener: (interval: TInterval, previousInterval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined, slide: boolean) => void): any; - (event: "addInterval" | "deleteInterval", listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined) => void): any; - (event: "propertyChanged", listener: (interval: TInterval, propertyDeltas: PropertySet, local: boolean, op: ISequencedDocumentMessage | undefined) => void): any; + (event: "changeInterval", listener: (interval: TInterval, previousInterval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined, slide: boolean) => void): void; + (event: "addInterval" | "deleteInterval", listener: (interval: TInterval, local: boolean, op: ISequencedDocumentMessage | undefined) => void): void; + (event: "propertyChanged", listener: (interval: TInterval, propertyDeltas: PropertySet, local: boolean, op: ISequencedDocumentMessage | undefined) => void): void; } // @internal @sealed @deprecated (undocumented) @@ -207,7 +210,7 @@ export interface InteriorSequencePlace { export class Interval implements ISerializableInterval { constructor(start: number, end: number, props?: PropertySet); // (undocumented) - addProperties(newProps: PropertySet, collaborating?: boolean, seq?: number, op?: ICombiningOp): PropertySet | undefined; + addProperties(newProps: PropertySet, collaborating?: boolean, seq?: number): PropertySet | undefined; addPropertySet(props: PropertySet): void; // (undocumented) auxProps: PropertySet[] | undefined; @@ -228,7 +231,7 @@ export class Interval implements ISerializableInterval { overlaps(b: Interval): boolean; properties: PropertySet; // (undocumented) - propertyManager: PropertiesManager; + readonly propertyManager: PropertiesManager; // (undocumented) serialize(): ISerializedInterval; // (undocumented) @@ -310,8 +313,6 @@ export type IntervalStickiness = (typeof IntervalStickiness)[keyof typeof Interv // @alpha (undocumented) export enum IntervalType { - // @deprecated (undocumented) - Nest = 1, // (undocumented) Simple = 0, SlideOnRemove = 2, @@ -326,6 +327,8 @@ export interface IOverlappingIntervalsIndex { operation: TOperation; @@ -369,16 +372,16 @@ export interface ISharedIntervalCollection void): any; + (event: "createIntervalCollection", listener: (label: string, local: boolean, target: IEventThisPlaceHolder) => void): void; // (undocumented) - (event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void): any; + (event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void): void; // (undocumented) - (event: "maintenance", listener: (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void): any; + (event: "maintenance", listener: (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void): void; } // @alpha export interface ISharedString extends SharedSegmentSequence { - insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): IMergeTreeInsertMsg | undefined; + insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): void; insertText(pos: number, text: string, props?: PropertySet): void; posFromRelativePos(relativePos: IRelativePosition): number; } @@ -395,6 +398,26 @@ export interface IValueOpEmitter { emit(opName: IntervalOpType, previousValue: undefined, params: SerializedIntervalDelta, localOpMetadata: IMapMessageLocalMetadata): void; } +export { LocalReferencePosition } + +export { MapLike } + +export { Marker } + +export { MergeTreeDeltaType } + +export { PropertySet } + +export { ReferencePosition } + +export { ReferenceType } + +export { reservedMarkerIdKey } + +export { reservedRangeLabelsKey } + +export { reservedTileLabelsKey } + // @internal export function revertSharedStringRevertibles(sharedString: SharedString, revertibles: SharedStringRevertible[]): void; @@ -426,7 +449,7 @@ export class SequenceInterval implements ISerializableInterval { end: LocalReferencePosition, intervalType: IntervalType, props?: PropertySet, startSide?: Side, endSide?: Side); addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void; // (undocumented) - addProperties(newProps: PropertySet, collab?: boolean, seq?: number, op?: ICombiningOp): PropertySet | undefined; + addProperties(newProps: PropertySet, collab?: boolean, seq?: number): PropertySet | undefined; // (undocumented) clone(): SequenceInterval; compare(b: SequenceInterval): number; @@ -537,7 +560,7 @@ export class SharedIntervalCollectionFactory implements IChannelFactory { // @alpha (undocumented) export abstract class SharedSegmentSequence extends SharedObject implements ISharedIntervalCollection, MergeTreeRevertibleDriver { constructor(dataStoreRuntime: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes, segmentFromSpec: (spec: IJSONSegment) => ISegment); - annotateRange(start: number, end: number, props: PropertySet, combiningOp?: ICombiningOp): void; + annotateRange(start: number, end: number, props: PropertySet): void; // (undocumented) protected applyStashedOp(content: any): unknown; // (undocumented) @@ -564,8 +587,6 @@ export abstract class SharedSegmentSequence extends SharedOb posAfterEnd: number | undefined; }; // @deprecated (undocumented) - getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap; - // @deprecated (undocumented) groupOperation(groupOp: IMergeTreeGroupMsg): void; protected guardReentrancy: (callback: () => TRet) => TRet; // (undocumented) @@ -580,6 +601,7 @@ export abstract class SharedSegmentSequence extends SharedOb get loaded(): Promise; protected loadedDeferred: Deferred; localReferencePositionToPosition(lref: ReferencePosition): number; + obliterateRange(start: number, end: number): void; // (undocumented) protected onConnect(): void; // (undocumented) @@ -590,15 +612,13 @@ export abstract class SharedSegmentSequence extends SharedOb protected processGCDataCore(serializer: SummarySerializer): void; removeLocalReferencePosition(lref: LocalReferencePosition): LocalReferencePosition | undefined; // (undocumented) - removeRange(start: number, end: number): IMergeTreeRemoveMsg; + removeRange(start: number, end: number): void; protected replaceRange(start: number, end: number, segment: ISegment): void; resolveRemoteClientPosition(remoteClientPosition: number, remoteClientRefSeq: number, remoteClientId: string): number | undefined; // (undocumented) protected reSubmitCore(content: any, localOpMetadata: unknown): void; // (undocumented) readonly segmentFromSpec: (spec: IJSONSegment) => ISegment; - // @deprecated (undocumented) - submitSequenceMessage(message: IMergeTreeOp): void; // (undocumented) protected summarizeCore(serializer: IFluidSerializer, telemetryContext?: ITelemetryContext): ISummaryTreeWithStats; walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: TClientData, splitRange?: boolean): void; @@ -620,9 +640,7 @@ export class SharedSequence extends SharedSegmentSequence> { // @alpha export class SharedString extends SharedSegmentSequence implements ISharedString { constructor(document: IFluidDataStoreRuntime, id: string, attributes: IChannelAttributes); - annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): void; - // @deprecated - annotateMarkerNotifyConsensus(marker: Marker, props: PropertySet, callback: (m: Marker) => void): void; + annotateMarker(marker: Marker, props: PropertySet): void; static create(runtime: IFluidDataStoreRuntime, id?: string): SharedString; // @deprecated findTile(startPos: number | undefined, tileLabel: string, preceding?: boolean): { @@ -637,13 +655,13 @@ export class SharedString extends SharedSegmentSequence imp getTextWithPlaceholders(start?: number, end?: number): string; // (undocumented) id: string; - insertMarker(pos: number, refType: ReferenceType, props?: PropertySet): IMergeTreeInsertMsg | undefined; + 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): IMergeTreeRemoveMsg; + 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; @@ -693,7 +711,7 @@ export class SubSequence extends BaseSegment { // (undocumented) protected createSplitSegmentAt(pos: number): SubSequence | undefined; // (undocumented) - static fromJSONObject(spec: Serializable): SubSequence | undefined; + static fromJSONObject(spec: any): SubSequence | undefined; // (undocumented) static is(segment: ISegment): segment is SubSequence; // (undocumented) @@ -710,4 +728,8 @@ export class SubSequence extends BaseSegment { static readonly typeString: string; } +export { TextSegment } + +export { TrackingGroup } + ``` diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index b87df0b9343c..cacddba98a32 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/sequence", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed sequence", "homepage": "https://fluidframework.com", "repository": { @@ -48,7 +48,6 @@ "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha", "test:newsnapfiles": "node dist/test/createSnapshotFiles.js", "test:stress": "cross-env FUZZ_TEST_COUNT=100 FUZZ_STRESS_RUN=true mocha --ignore \"dist/test/memory/**/*\" --recursive \"dist/test/**/*.fuzz.spec.js\" -r @fluidframework/mocha-test-setup", - "testfarm": "node dist/test/testFarm.js", "tsc": "tsc", "typetests:gen": "fluid-type-test-generator", "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" @@ -103,6 +102,7 @@ "@types/mocha": "^9.1.1", "@types/node": "^18.19.0", "@types/random-js": "^1.0.31", + "@types/uuid": "^9.0.2", "c8": "^7.7.1", "copyfiles": "^2.4.1", "cross-env": "^7.0.3", @@ -134,6 +134,26 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_ISharedString": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_SharedSegmentSequence": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_SharedSequence": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_SharedString": { + "forwardCompat": false, + "backCompat": false + }, + "TypeAliasDeclaration_SharedStringSegment": { + "backCompat": false + } + } } } diff --git a/packages/dds/sequence/src/defaultMapInterfaces.ts b/packages/dds/sequence/src/defaultMapInterfaces.ts index 021c1a8b9b0f..21a879edeee7 100644 --- a/packages/dds/sequence/src/defaultMapInterfaces.ts +++ b/packages/dds/sequence/src/defaultMapInterfaces.ts @@ -128,7 +128,7 @@ export interface IValueOperation { local: boolean, message: ISequencedDocumentMessage | undefined, localOpMetadata: IMapMessageLocalMetadata | undefined, - ); + ): void; /** * Rebases an `op` on `value` from its original perspective (ref/local seq) to the current @@ -174,7 +174,7 @@ export interface ISharedDefaultMapEvents extends ISharedObjectEvents { ( event: "valueChanged" | "create", listener: (changed: IValueChanged, local: boolean, target: IEventThisPlaceHolder) => void, - ); + ): void; } /** diff --git a/packages/dds/sequence/src/index.ts b/packages/dds/sequence/src/index.ts index 4f875e41be4b..c88bd6dabe97 100644 --- a/packages/dds/sequence/src/index.ts +++ b/packages/dds/sequence/src/index.ts @@ -85,3 +85,20 @@ export { SharedIntervalCollectionFactory, } from "./sharedIntervalCollection"; export { IJSONRunSegment, SharedSequence, SubSequence } from "./sharedSequence"; + +export { + ISegment, + Marker, + BaseSegment, + ReferencePosition, + ReferenceType, + PropertySet, + MapLike, + TextSegment, + MergeTreeDeltaType, + reservedMarkerIdKey, + reservedTileLabelsKey, + reservedRangeLabelsKey, + TrackingGroup, + LocalReferencePosition, +} from "@fluidframework/merge-tree"; diff --git a/packages/dds/sequence/src/intervalCollection.ts b/packages/dds/sequence/src/intervalCollection.ts index 4045289b93e0..14ff9a0f4937 100644 --- a/packages/dds/sequence/src/intervalCollection.ts +++ b/packages/dds/sequence/src/intervalCollection.ts @@ -690,7 +690,7 @@ export interface IIntervalCollectionEvent void, - ); + ): void; /** * This event is invoked whenever an interval is added or removed from the collection. * `local` reflects whether the change originated locally. @@ -703,7 +703,7 @@ export interface IIntervalCollectionEvent void, - ); + ): void; /** * This event is invoked whenever an interval's properties have changed. * `interval` reflects the state of the updated properties. @@ -721,7 +721,7 @@ export interface IIntervalCollectionEvent void, - ); + ): void; } // solely for type checking in the implementation of add - will be removed once @@ -869,7 +869,7 @@ export interface IIntervalCollection * @param props - Property set to apply to the interval. Shallow merging is used between any existing properties * and `prop`, i.e. the interval will end up with a property object equivalent to `{ ...oldProps, ...props }`. */ - changeProperties(id: string, props: PropertySet); + changeProperties(id: string, props: PropertySet): void; /** * Changes the endpoints of an existing interval. * @param id - Id of the interval to change diff --git a/packages/dds/sequence/src/intervalTree.ts b/packages/dds/sequence/src/intervalTree.ts index 419a9440eab3..26c74ddb1832 100644 --- a/packages/dds/sequence/src/intervalTree.ts +++ b/packages/dds/sequence/src/intervalTree.ts @@ -4,7 +4,6 @@ */ import { - IIntegerRange, RBNode, IRBAugmentation, IRBMatcher, @@ -17,8 +16,6 @@ export interface AugmentedIntervalNode { minmax: IInterval; } -export const integerRangeToString = (range: IIntegerRange) => `[${range.start},${range.end})`; - const intervalComparer = (a: IInterval, b: IInterval) => a.compare(b); export type IntervalNode = RBNode; diff --git a/packages/dds/sequence/src/intervals/interval.ts b/packages/dds/sequence/src/intervals/interval.ts index 99f7fb13ca7e..833d65632df7 100644 --- a/packages/dds/sequence/src/intervals/interval.ts +++ b/packages/dds/sequence/src/intervals/interval.ts @@ -5,7 +5,6 @@ /* eslint-disable import/no-deprecated */ import { - ICombiningOp, PropertiesManager, PropertySet, createMap, @@ -27,21 +26,21 @@ export class Interval implements ISerializableInterval { /** * {@inheritDoc ISerializableInterval.properties} */ - public properties: PropertySet; + public properties: PropertySet = createMap(); + /***/ public auxProps: PropertySet[] | undefined; + /** * {@inheritDoc ISerializableInterval.propertyManager} */ - public propertyManager: PropertiesManager; + public readonly propertyManager: PropertiesManager = new PropertiesManager(); + constructor( public start: number, public end: number, props?: PropertySet, ) { - this.propertyManager = new PropertiesManager(); - this.properties = {}; - if (props) { this.addProperties(props); } @@ -170,14 +169,11 @@ export class Interval implements ISerializableInterval { newProps: PropertySet, collaborating: boolean = false, seq?: number, - op?: ICombiningOp, ): PropertySet | undefined { if (newProps) { - this.initializeProperties(); return this.propertyManager.addProperties( this.properties, newProps, - op, seq, collaborating, ); @@ -208,7 +204,6 @@ export class Interval implements ISerializableInterval { } const newInterval = new Interval(startPos, endPos); if (this.properties) { - newInterval.initializeProperties(); this.propertyManager.copyTo( this.properties, newInterval.properties, @@ -217,15 +212,6 @@ export class Interval implements ISerializableInterval { } return newInterval; } - - private initializeProperties(): void { - if (!this.propertyManager) { - this.propertyManager = new PropertiesManager(); - } - if (!this.properties) { - this.properties = createMap(); - } - } } export function createInterval(label: string, start: SequencePlace, end: SequencePlace): Interval { diff --git a/packages/dds/sequence/src/intervals/intervalUtils.ts b/packages/dds/sequence/src/intervals/intervalUtils.ts index dc8ea13a611a..4c9263b26fce 100644 --- a/packages/dds/sequence/src/intervals/intervalUtils.ts +++ b/packages/dds/sequence/src/intervals/intervalUtils.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable no-bitwise */ import { + // eslint-disable-next-line import/no-deprecated Client, PropertiesManager, PropertySet, @@ -88,10 +88,6 @@ export type IntervalOpType = (typeof IntervalOpType)[keyof typeof IntervalOpType */ export enum IntervalType { Simple = 0x0, - /** - * @deprecated this functionality is no longer supported and will be removed - */ - Nest = 0x1, /** * SlideOnRemove indicates that the ends of the interval will slide if the segment @@ -225,6 +221,7 @@ export interface IIntervalHelpers { label: string, start: SequencePlace | undefined, end: SequencePlace | undefined, + // eslint-disable-next-line import/no-deprecated client: Client | undefined, intervalType: IntervalType, op?: ISequencedDocumentMessage, diff --git a/packages/dds/sequence/src/intervals/sequenceInterval.ts b/packages/dds/sequence/src/intervals/sequenceInterval.ts index 3280a7f8f034..aa15d78ce7dc 100644 --- a/packages/dds/sequence/src/intervals/sequenceInterval.ts +++ b/packages/dds/sequence/src/intervals/sequenceInterval.ts @@ -8,7 +8,6 @@ import { Client, - ICombiningOp, ISegment, LocalReferencePosition, PropertiesManager, @@ -106,11 +105,12 @@ export class SequenceInterval implements ISerializableInterval { /** * {@inheritDoc ISerializableInterval.properties} */ - public properties: PropertySet; + public properties: PropertySet = createMap(); + /** * {@inheritDoc ISerializableInterval.propertyManager} */ - public propertyManager: PropertiesManager; + public propertyManager: PropertiesManager = new PropertiesManager(); /***/ public get stickiness(): IntervalStickiness { @@ -141,9 +141,6 @@ export class SequenceInterval implements ISerializableInterval { public readonly startSide: Side = Side.Before, public readonly endSide: Side = Side.Before, ) { - this.propertyManager = new PropertiesManager(); - this.properties = {}; - if (props) { this.addProperties(props); } @@ -332,10 +329,8 @@ export class SequenceInterval implements ISerializableInterval { newProps: PropertySet, collab: boolean = false, seq?: number, - op?: ICombiningOp, ): PropertySet | undefined { - this.initializeProperties(); - return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab); + return this.propertyManager.addProperties(this.properties, newProps, seq, collab); } /** @@ -420,7 +415,6 @@ export class SequenceInterval implements ISerializableInterval { endSide ?? this.endSide, ); if (this.properties) { - newInterval.initializeProperties(); this.propertyManager.copyTo( this.properties, newInterval.properties, @@ -429,15 +423,6 @@ export class SequenceInterval implements ISerializableInterval { } return newInterval; } - - private initializeProperties(): void { - if (!this.propertyManager) { - this.propertyManager = new PropertiesManager(); - } - if (!this.properties) { - this.properties = createMap(); - } - } } export function createPositionReferenceFromSegoff( @@ -568,10 +553,6 @@ export function createSequenceInterval( beginRefType = ReferenceType.Transient; endRefType = ReferenceType.Transient; } else { - if (intervalType === IntervalType.Nest) { - beginRefType = ReferenceType.NestBegin; - endRefType = ReferenceType.NestEnd; - } // All non-transient interval references must eventually be SlideOnRemove // To ensure eventual consistency, they must start as StayOnRemove when // pending (created locally and creation op is not acked) diff --git a/packages/dds/sequence/src/packageVersion.ts b/packages/dds/sequence/src/packageVersion.ts index dd6b74a993fb..f3873e9df78c 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/sequence/src/revertibles.ts b/packages/dds/sequence/src/revertibles.ts index eecb0bf76a7f..e63fcecb1c4f 100644 --- a/packages/dds/sequence/src/revertibles.ts +++ b/packages/dds/sequence/src/revertibles.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable no-bitwise */ import { assert, unreachableCase } from "@fluidframework/core-utils"; @@ -19,6 +18,7 @@ import { ReferenceType, refTypeIncludesFlag, revertMergeTreeDeltaRevertibles, + // eslint-disable-next-line import/no-deprecated SortedSet, getSlideToSegoff, SlidingPreference, @@ -114,13 +114,19 @@ export function appendDeleteIntervalToRevertibles( string: SharedString, interval: SequenceInterval, revertibles: SharedStringRevertible[], -) { - const startSeg = interval.start.getSegment() as SharedStringSegment; +): SharedStringRevertible[] { + const startSeg = interval.start.getSegment() as SharedStringSegment | undefined; + if (!startSeg) { + return revertibles; + } const startType = startSeg.removedSeq !== undefined ? ReferenceType.SlideOnRemove | ReferenceType.RangeBegin : ReferenceType.StayOnRemove | ReferenceType.RangeBegin; - const endSeg = interval.end.getSegment() as SharedStringSegment; + const endSeg = interval.end.getSegment() as SharedStringSegment | undefined; + if (!endSeg) { + return revertibles; + } const endType = endSeg.removedSeq !== undefined ? ReferenceType.SlideOnRemove | ReferenceType.RangeEnd @@ -560,6 +566,7 @@ interface RangeInfo { length: number; } +// eslint-disable-next-line import/no-deprecated class SortedRangeSet extends SortedSet { protected getKey(item: RangeInfo): string { return item.ranges[0].segment.ordinal; diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 4eaa9109f43a..8fbb6daf2f34 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert, Deferred } from "@fluidframework/core-utils"; import { bufferToString } from "@fluid-internal/client-utils"; import { LoggingError, createChildLogger } from "@fluidframework/telemetry-utils"; @@ -15,12 +13,13 @@ import { IChannelStorageService, } from "@fluidframework/datastore-definitions"; import { + // eslint-disable-next-line import/no-deprecated Client, createAnnotateRangeOp, + // eslint-disable-next-line import/no-deprecated createGroupOp, createInsertOp, createRemoveRangeOp, - ICombiningOp, IJSONSegment, IMergeTreeAnnotateMsg, IMergeTreeDeltaOp, @@ -34,11 +33,12 @@ import { matchProperties, MergeTreeDeltaType, PropertySet, - RangeStackMap, ReferencePosition, ReferenceType, MergeTreeRevertibleDriver, SegmentGroup, + IMergeTreeObliterateMsg, + createObliterateRangeOp, SlidingPreference, } from "@fluidframework/merge-tree"; import { ObjectStoragePartition, SummaryTreeBuilder } from "@fluidframework/runtime-utils"; @@ -104,15 +104,15 @@ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { ( event: "createIntervalCollection", listener: (label: string, local: boolean, target: IEventThisPlaceHolder) => void, - ); + ): void; ( event: "sequenceDelta", listener: (event: SequenceDeltaEvent, target: IEventThisPlaceHolder) => void, - ); + ): void; ( event: "maintenance", listener: (event: SequenceMaintenanceEvent, target: IEventThisPlaceHolder) => void, - ); + ): void; } /** @@ -146,7 +146,7 @@ export abstract class SharedSegmentSequence switch (event.deltaOperation) { case MergeTreeDeltaType.ANNOTATE: { const lastAnnotate = ops[ops.length - 1] as IMergeTreeAnnotateMsg; - const props = {}; + const props: PropertySet = {}; for (const key of Object.keys(r.propertyDeltas)) { props[key] = r.segment.properties?.[key] ?? null; } @@ -162,7 +162,6 @@ export abstract class SharedSegmentSequence r.position, r.position + r.segment.cachedLength, props, - undefined, ), ); } @@ -189,12 +188,29 @@ export abstract class SharedSegmentSequence break; } + case MergeTreeDeltaType.OBLITERATE: { + const lastRem = ops[ops.length - 1] as IMergeTreeObliterateMsg; + if (lastRem?.pos1 === r.position) { + assert(lastRem.pos2 !== undefined, "pos2 should not be undefined here"); + lastRem.pos2 += r.segment.cachedLength; + } else { + ops.push( + createObliterateRangeOp( + r.position, + r.position + r.segment.cachedLength, + ), + ); + } + break; + } + default: } } return ops; } + // eslint-disable-next-line import/no-deprecated protected client: Client; /** `Deferred` that triggers once the object is loaded */ protected loadedDeferred = new Deferred(); @@ -232,6 +248,7 @@ export abstract class SharedSegmentSequence this.logger.sendErrorEvent({ eventName: "SequenceLoadFailed" }, error); }); + // eslint-disable-next-line import/no-deprecated this.client = new Client( segmentFromSpec, createChildLogger({ @@ -266,12 +283,25 @@ export abstract class SharedSegmentSequence * @param start - The inclusive start of the range to remove * @param end - The exclusive end of the range to remove */ - public removeRange(start: number, end: number): IMergeTreeRemoveMsg { - return this.guardReentrancy(() => this.client.removeRangeLocal(start, end)); + public removeRange(start: number, end: number): void { + this.guardReentrancy(() => this.client.removeRangeLocal(start, end)); } /** - * @deprecated The ability to create group ops will be removed in an upcoming release, as group ops are redundant with the native batching capabilities of the runtime + * Obliterate is similar to remove, but differs in that segments concurrently + * inserted into an obliterated range will also be removed + * + * @param start - The inclusive start of the range to obliterate + * @param end - The exclusive end of the range to obliterate + */ + public obliterateRange(start: number, end: number): void { + this.guardReentrancy(() => this.client.obliterateRangeLocal(start, end)); + } + + /** + * @deprecated The ability to create group ops will be removed in an upcoming + * release, as group ops are redundant with the native batching capabilities + * of the runtime */ public groupOperation(groupOp: IMergeTreeGroupMsg) { this.guardReentrancy(() => this.client.localTransaction(groupOp)); @@ -311,16 +341,10 @@ export abstract class SharedSegmentSequence * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * */ - public annotateRange( - start: number, - end: number, - props: PropertySet, - combiningOp?: ICombiningOp, - ) { - this.guardReentrancy(() => this.client.annotateRangeLocal(start, end, props, combiningOp)); + public annotateRange(start: number, end: number, props: PropertySet): void { + this.guardReentrancy(() => this.client.annotateRangeLocal(start, end, props)); } public getPropertiesAtPosition(pos: number) { @@ -397,10 +421,7 @@ export abstract class SharedSegmentSequence ); } - /** - * @deprecated This method will no longer be public in an upcoming release as it is not safe to use outside of this class - */ - public submitSequenceMessage(message: IMergeTreeOp) { + private submitSequenceMessage(message: IMergeTreeOp) { if (!this.isAttached()) { return; } @@ -411,7 +432,7 @@ export abstract class SharedSegmentSequence // if loading isn't complete, we need to cache // local ops until loading is complete, and then - // they will be resent + // they will be present if (!this.loadedDeferred.isCompleted) { this.loadedDeferredOutgoingOps.push(metadata ? [translated, metadata] : translated); } else { @@ -430,12 +451,13 @@ export abstract class SharedSegmentSequence /** * Walk the underlying segments of the sequence. - * The walked segments may extend beyond the range - * if the segments cross the ranges start or end boundaries. - * Set split range to true to ensure only segments within the - * range are walked. + * The walked segments may extend beyond the range if the segments cross the + * ranges start or end boundaries. + * + * Set split range to true to ensure only segments within the range are walked. * - * @param handler - The function to handle each segment + * @param handler - The function to handle each segment. Traversal ends if + * this function returns true. * @param start - Optional. The start of range walk. * @param end - Optional. The end of range walk * @param accum - Optional. An object that will be passed to the handler for accumulation @@ -451,13 +473,6 @@ export abstract class SharedSegmentSequence this.client.walkSegments(handler, start, end, accum as TClientData, splitRange); } - /** - * @deprecated this functionality is no longer supported and will be removed - */ - public getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap { - return this.client.getStackContext(startPos, rangeLabels); - } - /** * @returns The most recent sequence number which has been acked by the server and processed by this * SharedSegmentSequence. @@ -471,7 +486,7 @@ export abstract class SharedSegmentSequence * @param refPos - The reference position to insert the segment at * @param segment - The segment to insert */ - public insertAtReferencePosition(pos: ReferencePosition, segment: T) { + public insertAtReferencePosition(pos: ReferencePosition, segment: T): void { this.guardReentrancy(() => this.client.insertAtReferencePositionLocal(pos, segment)); } /** @@ -479,7 +494,7 @@ export abstract class SharedSegmentSequence * @param start - The position to insert the segment at * @param spec - The segment to inserts spec */ - public insertFromSpec(pos: number, spec: IJSONSegment) { + public insertFromSpec(pos: number, spec: IJSONSegment): void { const segment = this.segmentFromSpec(spec); this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment)); } @@ -549,7 +564,7 @@ export abstract class SharedSegmentSequence * @param end - The end of the range to replace * @param segment - The segment that will replace the range */ - protected replaceRange(start: number, end: number, segment: ISegment) { + protected replaceRange(start: number, end: number, segment: ISegment): void { // Insert at the max end of the range when start > end, but still remove the range later const insertIndex: number = Math.max(start, end); @@ -763,6 +778,7 @@ export abstract class SharedSegmentSequence stashMessage = { ...message, referenceSequenceNumber: stashMessage.sequenceNumber - 1, + // eslint-disable-next-line import/no-deprecated contents: ops.length !== 1 ? createGroupOp(...ops) : ops[0], }; } diff --git a/packages/dds/sequence/src/sequenceDeltaEvent.ts b/packages/dds/sequence/src/sequenceDeltaEvent.ts index 0b467de3ebf8..4ff6d6fa93be 100644 --- a/packages/dds/sequence/src/sequenceDeltaEvent.ts +++ b/packages/dds/sequence/src/sequenceDeltaEvent.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { + // eslint-disable-next-line import/no-deprecated Client, IMergeTreeDeltaCallbackArgs, IMergeTreeDeltaOpArgs, @@ -16,6 +15,7 @@ import { MergeTreeDeltaOperationTypes, MergeTreeMaintenanceType, PropertySet, + // eslint-disable-next-line import/no-deprecated SortedSegmentSet, } from "@fluidframework/merge-tree"; @@ -31,12 +31,14 @@ export abstract class SequenceEvent< TOperation extends MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationTypes, > { public readonly deltaOperation: TOperation; + // eslint-disable-next-line import/no-deprecated private readonly sortedRanges: Lazy>>; private readonly pFirst: Lazy>; private readonly pLast: Lazy>; constructor( public readonly deltaArgs: IMergeTreeDeltaCallbackArgs, + // eslint-disable-next-line import/no-deprecated private readonly mergeTreeClient: Client, ) { assert( @@ -45,7 +47,9 @@ export abstract class SequenceEvent< ); this.deltaOperation = deltaArgs.operation; + // eslint-disable-next-line import/no-deprecated this.sortedRanges = new Lazy>>(() => { + // eslint-disable-next-line import/no-deprecated const set = new SortedSegmentSet>(); this.deltaArgs.deltaSegments.forEach((delta) => { const newRange: ISequenceDeltaRange = { @@ -119,6 +123,7 @@ export class SequenceDeltaEvent extends SequenceEvent extends BaseSegment { public static is(segment: ISegment): segment is SubSequence { return segment.type === SubSequence.typeString; } - public static fromJSONObject(spec: Serializable) { + public static fromJSONObject(spec: any) { if (spec && typeof spec === "object" && "items" in spec) { const segment = new SubSequence(spec.items); if (spec.props) { @@ -76,7 +76,10 @@ export class SubSequence extends BaseSegment { public append(segment: ISegment) { assert(SubSequence.is(segment), 0x448 /* can only append to another run segment */); super.append(segment); - this.items = this.items.concat(segment.items); + // assert above checks that segment is a SubSequence but not that generic T matches. + // Since SubSequence is already deprecated, assume that usage is generic T consistent + // and just cast here to satisfy concat. + this.items = this.items.concat((segment as SubSequence).items); } // TODO: retain removed items for undo @@ -169,7 +172,10 @@ export class SharedSequence extends SharedSegmentSequence> { if (firstSegment === undefined) { firstSegment = segment; } - items.push(...segment.items); + // Condition above checks that segment is a SubSequence but not that + // generic T matches. Since SubSequence is already deprecated, assume + // that walk only has SubSequence segments and just cast here. + items.push(...(segment as SubSequence).items); } return true; }, diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 54417b82f6dc..edcca50d8429 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -4,9 +4,6 @@ */ import { - ICombiningOp, - IMergeTreeInsertMsg, - IMergeTreeRemoveMsg, IMergeTreeTextHelper, IRelativePosition, ISegment, @@ -41,11 +38,7 @@ export interface ISharedString extends SharedSegmentSequence this.client.insertSegmentLocal(pos, segment)); + this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment)); } /** @@ -147,7 +136,11 @@ export class SharedString * @param text - The text to insert * @param props - The properties of text */ - public insertTextRelative(relativePos1: IRelativePosition, text: string, props?: PropertySet) { + public insertTextRelative( + relativePos1: IRelativePosition, + text: string, + props?: PropertySet, + ): void { const segment = new TextSegment(text); if (props) { segment.addProperties(props); @@ -160,7 +153,7 @@ export class SharedString /** * {@inheritDoc ISharedString.insertText} */ - public insertText(pos: number, text: string, props?: PropertySet) { + public insertText(pos: number, text: string, props?: PropertySet): void { const segment = new TextSegment(text); if (props) { segment.addProperties(props); @@ -176,7 +169,7 @@ export class SharedString * @param text - The text to replace the range with * @param props - Optional. The properties of the replacement text */ - public replaceText(start: number, end: number, text: string, props?: PropertySet) { + public replaceText(start: number, end: number, text: string, props?: PropertySet): void { this.replaceRange(start, end, TextSegment.make(text, props)); } @@ -186,38 +179,17 @@ export class SharedString * @param end - The exclusive end of the range to replace * @returns the message sent. */ - public removeText(start: number, end: number): IMergeTreeRemoveMsg { - return this.removeRange(start, end); - } - - /** - * Annotates the marker with the provided properties and calls the callback on consensus. - * @param marker - The marker to annotate - * @param props - The properties to annotate the marker with - * @param consensusCallback - The callback called when consensus is reached - * - * @deprecated We no longer intend to support this functionality and it will - * be removed in a future release. There is no replacement for this - * functionality. - */ - public annotateMarkerNotifyConsensus( - marker: Marker, - props: PropertySet, - callback: (m: Marker) => void, - ) { - this.guardReentrancy(() => - this.client.annotateMarkerNotifyConsensus(marker, props, callback), - ); + public removeText(start: number, end: number): void { + this.removeRange(start, end); } /** * Annotates the marker with the provided properties. * @param marker - The marker to annotate * @param props - The properties to annotate the marker with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. */ - public annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp) { - this.guardReentrancy(() => this.client.annotateMarker(marker, props, combiningOp)); + public annotateMarker(marker: Marker, props: PropertySet) { + this.guardReentrancy(() => this.client.annotateMarker(marker, props)); } /** @@ -430,10 +402,8 @@ const gatherTextAndMarkers: ISegmentAction = ( } else { if (placeholder && placeholder.length > 0) { const placeholderText = - placeholder === "*" - ? // eslint-disable-next-line @typescript-eslint/no-base-to-string - `\n${segment.toString()}` - : placeholder.repeat(segment.cachedLength); + // eslint-disable-next-line @typescript-eslint/no-base-to-string + placeholder === "*" ? `\n${segment}` : placeholder.repeat(segment.cachedLength); textSegment.text += placeholderText; } else { const marker = segment as Marker; diff --git a/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts b/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts index 6cd08818dfec..1a3e9fbc329c 100644 --- a/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts +++ b/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts @@ -52,6 +52,10 @@ export interface RemoveRange extends RangeSpec { type: "removeRange"; } +export interface ObliterateRange extends RangeSpec { + type: "obliterateRange"; +} + // For non-interval collection fuzzing, annotating text would also be useful. export interface AddInterval extends IntervalCollectionSpec, RangeSpec { type: "addInterval"; @@ -90,6 +94,7 @@ export interface RevertibleWeights { revertWeight: number; addText: number; removeRange: number; + obliterateRange: number; addInterval: number; deleteInterval: number; changeInterval: number; @@ -98,7 +103,7 @@ export interface RevertibleWeights { export type IntervalOperation = AddInterval | ChangeInterval | DeleteInterval | ChangeProperties; export type OperationWithRevert = IntervalOperation | RevertSharedStringRevertibles; -export type TextOperation = AddText | RemoveRange; +export type TextOperation = AddText | RemoveRange | ObliterateRange; export type ClientOperation = IntervalOperation | TextOperation; @@ -117,6 +122,7 @@ export interface SharedStringOperationGenerationConfig { weights?: { addText: number; removeRange: number; + obliterateRange: number; }; } @@ -139,6 +145,7 @@ export const defaultSharedStringOperationGenerationConfig: Required = @@ -155,6 +162,7 @@ export const defaultIntervalOperationGenerationConfig: Required { client.channel.removeRange(start, end); }, + obliterateRange: async ({ client }, { start, end }) => { + client.channel.obliterateRange(start, end); + }, addInterval: async ({ client }, { start, end, collectionName, id, startSide, endSide }) => { const collection = client.channel.getIntervalCollection(collectionName); collection.add({ @@ -280,6 +291,13 @@ export function createSharedStringGeneratorOperations( }; } + async function obliterateRange(state: ClientOpState): Promise { + return { + type: "obliterateRange", + ...exclusiveRange(state), + }; + } + async function removeRange(state: ClientOpState): Promise { return { type: "removeRange", ...exclusiveRange(state) }; } @@ -300,6 +318,7 @@ export function createSharedStringGeneratorOperations( exclusiveRange, exclusiveRangeLeaveChar, addText, + obliterateRange, removeRange, removeRangeLeaveChar, lengthSatisfies, @@ -316,11 +335,13 @@ export class SharedStringFuzzFactory extends SharedStringFactory { attributes: IChannelAttributes, ): Promise { runtime.options.intervalStickinessEnabled = true; + runtime.options.mergeTreeEnableObliterate = true; return super.load(runtime, id, services, attributes); } public create(document: IFluidDataStoreRuntime, id: string): SharedString { document.options.intervalStickinessEnabled = true; + document.options.mergeTreeEnableObliterate = true; return super.create(document, id); } } diff --git a/packages/dds/sequence/src/test/fuzz/intervalCollection.fuzz.spec.ts b/packages/dds/sequence/src/test/fuzz/intervalCollection.fuzz.spec.ts index f56c4f0b5c15..a9ce719d1181 100644 --- a/packages/dds/sequence/src/test/fuzz/intervalCollection.fuzz.spec.ts +++ b/packages/dds/sequence/src/test/fuzz/intervalCollection.fuzz.spec.ts @@ -38,6 +38,7 @@ export function makeOperationGenerator( const { startPosition, addText, + obliterateRange, removeRange, removeRangeLeaveChar, lengthSatisfies, @@ -173,6 +174,7 @@ export function makeOperationGenerator( }) : hasNonzeroLength, ], + [obliterateRange, usableWeights.obliterateRange, hasNonzeroLength], [addInterval, usableWeights.addInterval, all(hasNotTooManyIntervals, hasNonzeroLength)], [deleteInterval, usableWeights.deleteInterval, hasAnInterval], [changeInterval, usableWeights.changeInterval, all(hasAnInterval, hasNonzeroLength)], @@ -194,8 +196,6 @@ describe("IntervalCollection fuzz testing", () => { createDDSFuzzSuite(model, { ...defaultFuzzOptions, - // AB#4477: Seed 20 and others with its call stack is the same root cause as skipped regression test in - // intervalCollection.spec.ts--search for 4477. // The other failing seeds were added when the mocks were changed to properly update msn on reconnects. // This exposed ways that `0x54e` can occur. // The root cause of this bug is--roughly speaking--interval endpoints with StayOnRemove being placed @@ -203,7 +203,7 @@ describe("IntervalCollection fuzz testing", () => { // TODO:AB#5337: re-enable these seeds. skip: [ 1, 2, 4, 9, 10, 11, 12, 14, 16, 19, 21, 23, 24, 26, 27, 32, 33, 39, 40, 43, 44, 45, 46, - 47, 48, 50, 51, 53, 55, 62, 69, 71, 72, 73, 74, 81, 82, 84, 86, 88, 89, 93, 95, 96, + 47, 48, 50, 51, 53, 55, 62, 63, 69, 71, 72, 73, 74, 81, 82, 84, 86, 88, 89, 93, 95, 96, ], // Uncomment this line to replay a specific seed from its failure file: // replay: 0, @@ -240,8 +240,7 @@ describe("IntervalCollection fuzz testing with rebased batches", () => { createDDSFuzzSuite(noReconnectWithRebaseModel, { ...defaultFuzzOptions, - // AB#4477: Either the same root cause as skipped regression test in intervalCollection.spec.ts--search for 4477, - // or 0x54e, see AB#5337 or comment on "default interval collection" fuzz suite. + // 0x54e: See AB#5337 or comment on "default interval collection" fuzz suite. skip: [3, 9, 11, 13, 23, 26, 29, 30, 31, 32, 36, 39, 41, 46, 49, 52, 53, 71, 73, 81, 86], reconnectProbability: 0.0, clientJoinOptions: { diff --git a/packages/dds/sequence/src/test/fuzz/intervalRevertibles.fuzz.spec.ts b/packages/dds/sequence/src/test/fuzz/intervalRevertibles.fuzz.spec.ts index c4e35dec4af1..56f1ee90aa24 100644 --- a/packages/dds/sequence/src/test/fuzz/intervalRevertibles.fuzz.spec.ts +++ b/packages/dds/sequence/src/test/fuzz/intervalRevertibles.fuzz.spec.ts @@ -141,6 +141,7 @@ describe("IntervalCollection fuzz testing", () => { revertWeight: 2, addText: 2, removeRange: 1, + obliterateRange: 0, addInterval: 2, deleteInterval: 2, changeInterval: 2, @@ -167,6 +168,7 @@ describe("IntervalCollection fuzz testing with rebasing", () => { revertWeight: 2, addText: 2, removeRange: 1, + obliterateRange: 0, addInterval: 2, deleteInterval: 2, changeInterval: 2, diff --git a/packages/dds/sequence/src/test/intervalCollection.spec.ts b/packages/dds/sequence/src/test/intervalCollection.spec.ts index dd23b50c52b4..8ab661e3b318 100644 --- a/packages/dds/sequence/src/test/intervalCollection.spec.ts +++ b/packages/dds/sequence/src/test/intervalCollection.spec.ts @@ -188,9 +188,13 @@ describe("SharedString interval collections", () => { // Regression test for bug described in // - // this test involves a crash inside RBTree when multiple intervals slide + // This test involves a crash inside RBTree when multiple intervals slide // off the string - it.skip("passes regression test for #4477", () => { + // + // More specifically, previously we didn't properly clear the segment + // on local references which became detached, which caused crashes on + // some IntervalCollection workflows + it("passes regression test for #4477", () => { sharedString.insertText(0, "ABC"); sharedString.insertText(0, "D"); // DABC diff --git a/packages/dds/sequence/src/test/intervalRebasing.spec.ts b/packages/dds/sequence/src/test/intervalRebasing.spec.ts index 9c4c8e806555..6c74d485076a 100644 --- a/packages/dds/sequence/src/test/intervalRebasing.spec.ts +++ b/packages/dds/sequence/src/test/intervalRebasing.spec.ts @@ -24,6 +24,7 @@ function constructClients( const dataStoreRuntime = new MockFluidDataStoreRuntime(); dataStoreRuntime.options = { intervalStickinessEnabled: true, + mergeTreeEnableObliterate: true, }; const sharedString = new SharedString( dataStoreRuntime, @@ -129,6 +130,35 @@ describe("interval rebasing", () => { assertConsistent(clients); }); + it("handles basic interval sliding for obliterate", () => { + // A-(BC) + + clients[0].sharedString.insertText(0, "ABC"); + const collection_0 = clients[0].sharedString.getIntervalCollection("comments"); + collection_0.add(0, 2, IntervalType.SlideOnRemove, { + intervalId: "a", + }); + clients[0].sharedString.obliterateRange(1, 3); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + + it("reference is -1 for obliterated segment", () => { + // (L-PC-F) + + clients[1].sharedString.insertText(0, "F"); + clients[0].sharedString.insertText(0, "PC"); + const collection_0 = clients[0].sharedString.getIntervalCollection("comments"); + collection_0.add(0, 1, IntervalType.SlideOnRemove, { + intervalId: "a", + }); + clients[1].sharedString.insertText(0, "L"); + clients[1].sharedString.obliterateRange(0, 2); + + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + it("slides to correct final destination", () => { clients[0].sharedString.insertText(0, "A"); containerRuntimeFactory.processAllMessages(); @@ -198,6 +228,115 @@ describe("interval rebasing", () => { assertConsistent(clients); }); + it("keeps obliterate segment group the same across multiple reconnects", () => { + // A-C + // (A-B-C) + clients[0].sharedString.insertText(0, "C"); + clients[0].sharedString.insertText(0, "A"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].sharedString.insertText(1, "B"); + clients[1].sharedString.obliterateRange(0, 2); + clients[1].containerRuntime.connected = false; + clients[1].containerRuntime.connected = true; + clients[1].containerRuntime.connected = false; + clients[1].containerRuntime.connected = true; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + + it("doesn't crash for empty pending segment group", () => { + // A + // ((A))-[D] + clients[0].sharedString.insertText(0, "A"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[1].sharedString.obliterateRange(0, 1); + clients[0].sharedString.insertText(1, "D"); + clients[0].sharedString.obliterateRange(0, 1); + clients[0].sharedString.removeRange(0, 1); + clients[0].containerRuntime.connected = false; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].containerRuntime.connected = true; + + assert.equal(clients[0].sharedString.getText(), ""); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + + it("doesn't create empty segment group when obliterated segment was obliterated by other client during reconnect", () => { + // A + // ((A))-[D] + clients[0].sharedString.insertText(0, "A"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[1].sharedString.obliterateRange(0, 1); + clients[0].sharedString.insertText(1, "D"); + clients[0].sharedString.obliterateRange(0, 1); + clients[0].sharedString.removeRange(0, 1); + clients[0].containerRuntime.connected = false; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].containerRuntime.connected = true; + clients[0].containerRuntime.connected = false; + clients[0].containerRuntime.connected = true; + + assert.equal(clients[0].sharedString.getText(), ""); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + + // todo: a failing obliterate reconnect test. when rebasing the op, + // the character "C" has been concurrently obliterated, so the reconnect + // position of "B" is computed to be 0, rather than 1 + // + // at the time of writing, i'm not sure of a good solution. either we could + // change calculation of reconnection position in some way or we could not + // concurrently obliterate "C" in this context. + // + // in both cases, it's not clear to me how we detect when we're reconnecting + // + // ADO#3714 + it.skip("...", () => { + // AB + // A-C-B + clients[0].sharedString.insertText(0, "AB"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].sharedString.insertText(1, "C"); + clients[1].containerRuntime.connected = false; + clients[1].sharedString.obliterateRange(0, 2); + clients[1].containerRuntime.connected = true; + clients[1].containerRuntime.connected = false; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[1].containerRuntime.connected = true; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + + // todo: ADO#3714 Failing obliterate reconnect test + it.skip("...", () => { + clients[0].sharedString.insertText(0, "AB"); + clients[1].sharedString.insertText(0, "CD"); + clients[1].sharedString.insertText(1, "E"); + clients[0].sharedString.obliterateRange(0, 1); + clients[0].sharedString.insertText(0, "FGHIJK"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].sharedString.insertText(4, "L"); + clients[2].sharedString.obliterateRange(3, 5); + clients[0].containerRuntime.connected = false; + clients[0].sharedString.obliterateRange(1, 2); + clients[0].sharedString.insertText(7, "M"); + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + clients[0].containerRuntime.connected = true; + containerRuntimeFactory.processAllMessages(); + assertConsistent(clients); + }); + it("slides two refs on same segment to different segments", () => { clients[0].sharedString.insertText(0, "AB"); clients[0].sharedString.insertText(0, "C"); diff --git a/packages/dds/sequence/src/test/intervalTestUtils.ts b/packages/dds/sequence/src/test/intervalTestUtils.ts index 407b86985f62..8e16049cabec 100644 --- a/packages/dds/sequence/src/test/intervalTestUtils.ts +++ b/packages/dds/sequence/src/test/intervalTestUtils.ts @@ -32,8 +32,8 @@ export function assertConsistent(clients: Client[]): void { } export function assertEquivalentSharedStrings(a: SharedString, b: SharedString) { - assert.equal(a.getLength(), b.getLength()); assert.equal(a.getText(), b.getText(), `Non-equal text between strings ${a.id} and ${b.id}.`); + assert.equal(a.getLength(), b.getLength()); const firstLabels = Array.from(a.getIntervalCollectionLabels()).sort(); const otherLabels = Array.from(b.getIntervalCollectionLabels()).sort(); assert.deepEqual( diff --git a/packages/dds/sequence/src/test/reentrancy.spec.ts b/packages/dds/sequence/src/test/reentrancy.spec.ts index f2bd9d5a829f..cf046c40055b 100644 --- a/packages/dds/sequence/src/test/reentrancy.spec.ts +++ b/packages/dds/sequence/src/test/reentrancy.spec.ts @@ -4,7 +4,11 @@ */ import { strict as assert } from "assert"; -import { MergeTreeDeltaType } from "@fluidframework/merge-tree"; +import { + LocalReferenceCollection, + MergeTreeDeltaType, + ReferenceType, +} from "@fluidframework/merge-tree"; import { MockFluidDataStoreRuntime, MockContainerRuntimeFactory, @@ -114,6 +118,80 @@ describe("SharedString op-reentrancy", () => { } }); + it("is empty after deleting reference pos in reentrant callback", () => { + sharedString.insertText(0, "abcX"); + const { segment } = sharedString.getContainingSegment(0); + assert(segment); + segment.localRefs ??= new LocalReferenceCollection(segment); + const localRef = segment.localRefs.createLocalRef( + 0, + ReferenceType.SlideOnRemove, + undefined, + ); + + assert.notEqual(localRef.getSegment(), undefined); + + containerRuntimeFactory.processAllMessages(); + + sharedString.on("sequenceDelta", ({ deltaOperation, isLocal }, target) => { + if (deltaOperation === MergeTreeDeltaType.INSERT && isLocal) { + const { segment: segment2 } = target.getContainingSegment(0); + assert(segment2); + assert.equal(segment, segment2); + assert(segment2.localRefs); + segment2.localRefs.removeLocalRef(localRef); + } + }); + + sharedString.insertText(4, "e"); + containerRuntimeFactory.processAllMessages(); + + sharedString.walkSegments((seg) => { + if (!seg.localRefs) { + return false; + } + assert.equal(seg.localRefs.empty, true); + assert.equal(seg.localRefs.has(localRef), false); + return false; + }); + + assert.equal(localRef.getSegment(), undefined); + assert.equal(localRef.getOffset(), 0); + }); + + it("is empty after deleting segment containing simple reference pos in reentrant callback", () => { + sharedString.insertText(0, "abcX"); + const { segment } = sharedString.getContainingSegment(0); + assert(segment); + segment.localRefs ??= new LocalReferenceCollection(segment); + const localRef = segment.localRefs.createLocalRef(0, ReferenceType.Simple, undefined); + + assert.notEqual(localRef.getSegment(), undefined); + + containerRuntimeFactory.processAllMessages(); + + sharedString.on("sequenceDelta", ({ deltaOperation, isLocal }, target) => { + if (deltaOperation === MergeTreeDeltaType.INSERT && isLocal) { + target.removeRange(0, 4); + } + }); + + sharedString.insertText(4, "e"); + containerRuntimeFactory.processAllMessages(); + + sharedString.walkSegments((seg) => { + if (!seg.localRefs) { + return false; + } + assert.equal(seg.localRefs.empty, true); + assert.equal(seg.localRefs.has(localRef), false); + return false; + }); + + assert.equal(localRef.getSegment(), undefined); + assert.equal(localRef.getOffset(), 0); + }); + it("logs reentrant events a fixed number of times", () => { let numberRemainingReentrantInserts = 10; sharedString.on("sequenceDelta", ({ isLocal }, target) => { diff --git a/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts b/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts index 0a35e9d250e3..ea9040190b6b 100644 --- a/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts +++ b/packages/dds/sequence/src/test/sequenceDeltaEvent.spec.ts @@ -230,7 +230,7 @@ describe("non-collab", () => { client.on("delta", (opArgs, delta) => { deltaArgs = delta; }); - const op = client.annotateRangeLocal(start, end, newProps, undefined); + const op = client.annotateRangeLocal(start, end, newProps); assert(deltaArgs); assert.equal(deltaArgs.deltaSegments.length, expected.length); @@ -1378,7 +1378,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }), currentSeqNumber + 1, currentSeqNumber, // refseqnum ); @@ -1397,7 +1397,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bar" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bar" }), currentSeqNumber + 2, currentSeqNumber, // refseq remoteUserId, @@ -1430,7 +1430,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }), currentSeqNumber + 2, currentSeqNumber, // refseqnum ); @@ -1447,7 +1447,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bar" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bar" }), currentSeqNumber + 1, currentSeqNumber, // refseq remoteUserId, @@ -1481,7 +1481,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }), currentSeqNumber + 1, currentSeqNumber, // refseqnum ); @@ -1500,7 +1500,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bardash" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bardash" }), currentSeqNumber + 2, currentSeqNumber, // refseq remoteUserId, @@ -1533,7 +1533,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo: "bar" }), currentSeqNumber + 2, currentSeqNumber, // refseqnum ); @@ -1550,7 +1550,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bardash" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo: "bardash" }), currentSeqNumber + 1, currentSeqNumber, // refseq remoteUserId, @@ -1584,7 +1584,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo1: "bar1" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo1: "bar1" }), currentSeqNumber + 1, currentSeqNumber, // refseqnum ); @@ -1603,7 +1603,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo2: "bar2" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo2: "bar2" }), currentSeqNumber + 2, currentSeqNumber, // refseq remoteUserId, @@ -1636,7 +1636,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal(localPosStart, localPosEnd, { foo1: "bar1" }, undefined), + client.annotateRangeLocal(localPosStart, localPosEnd, { foo1: "bar1" }), currentSeqNumber + 2, currentSeqNumber, // refseqnum ); @@ -1653,7 +1653,7 @@ describe("collab", () => { ]); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo2: "bar2" }, undefined), + createAnnotateRangeOp(remotePosStart, remotePosEnd, { foo2: "bar2" }), currentSeqNumber + 1, currentSeqNumber, // refseq remoteUserId, @@ -1727,12 +1727,7 @@ describe("collab", () => { }); const localMessage1 = client.makeOpMessage( - client.annotateRangeLocal( - secondWordStart, - secondWordEnd, - { foo1: "bar1" }, - undefined, - ), + client.annotateRangeLocal(secondWordStart, secondWordEnd, { foo1: "bar1" }), currentSeqNumber + 1, currentSeqNumber, // refseqnum ); @@ -1751,12 +1746,7 @@ describe("collab", () => { ]); const localMessage2 = client.makeOpMessage( - client.annotateRangeLocal( - fourthWordStart, - fourthWordEnd, - { foo3: "bar3" }, - undefined, - ), + client.annotateRangeLocal(fourthWordStart, fourthWordEnd, { foo3: "bar3" }), currentSeqNumber + 2, currentSeqNumber + 1, // refseqnum ); @@ -1774,7 +1764,7 @@ describe("collab", () => { ]); const remoteMessage1 = client.makeOpMessage( - createAnnotateRangeOp(thirdWordStart, thirdWordEnd, { foo2: "bar2" }, undefined), + createAnnotateRangeOp(thirdWordStart, thirdWordEnd, { foo2: "bar2" }), currentSeqNumber + 3, currentSeqNumber, // refseq remoteUserId, @@ -1800,7 +1790,7 @@ describe("collab", () => { }); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(firstWordStart, fourthWordEnd, { foo: "bar" }, undefined), + createAnnotateRangeOp(firstWordStart, fourthWordEnd, { foo: "bar" }), seqnum, refseqnum, remoteUserId, @@ -1862,12 +1852,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal( - firstWordStart, - secondWordEnd, - { foo: "bar1" }, - undefined, - ), + client.annotateRangeLocal(firstWordStart, secondWordEnd, { foo: "bar1" }), seqnum, refseqnum, ); @@ -1900,7 +1885,7 @@ describe("collab", () => { }); const remoteMessage = client.makeOpMessage( - createAnnotateRangeOp(thirdWordStart, fourthWordEnd, { foo: "bar2" }, undefined), + createAnnotateRangeOp(thirdWordStart, fourthWordEnd, { foo: "bar2" }), seqnum, refseqnum, remoteUserId, @@ -1941,12 +1926,7 @@ describe("collab", () => { }); const localMessage = client.makeOpMessage( - client.annotateRangeLocal( - secondWordStart, - fourthWordEnd, - { foo: "bar3" }, - undefined, - ), + client.annotateRangeLocal(secondWordStart, fourthWordEnd, { foo: "bar3" }), seqnum, refseqnum, ); @@ -3212,7 +3192,6 @@ describe("SequenceDeltaEvent", () => { { foo: "bar", }, - undefined, ); assert(deltaArgs); diff --git a/packages/dds/sequence/src/test/subSequence.spec.ts b/packages/dds/sequence/src/test/subSequence.spec.ts index a6158d12e25b..e06bad5c4232 100644 --- a/packages/dds/sequence/src/test/subSequence.spec.ts +++ b/packages/dds/sequence/src/test/subSequence.spec.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable @typescript-eslint/no-base-to-string */ - import { assert } from "@fluidframework/core-utils"; import { Serializable } from "@fluidframework/datastore-definitions"; import { @@ -73,15 +71,18 @@ describe("SubSequence", () => { cli.insertItemsRemote(0, [2, 11], undefined, 1, 0, "1"); if (verbose) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(cli.mergeTree.toString()); } cli.insertItemsRemote(0, [4, 5, 6], undefined, 2, 0, "2"); if (verbose) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(cli.mergeTree.toString()); } const segment = new SubSequence([3, 4, 1, 1]); const insert = cli.insertSegmentLocal(4, segment); if (verbose) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(cli.mergeTree.toString()); } if (verbose) { @@ -95,6 +96,7 @@ describe("SubSequence", () => { cli.insertItemsRemote(5, [1, 5, 6, 2, 3], undefined, 4, 2, "2"); cli.insertItemsRemote(0, [9], undefined, 5, 0, "2"); if (verbose) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(cli.mergeTree.toString()); for (let clientId = 0; clientId < 4; clientId++) { for (let refSeq = 0; refSeq < 6; refSeq++) { @@ -104,6 +106,7 @@ describe("SubSequence", () => { } cli.applyMsg(cli.makeOpMessage(createRemoveRangeOp(3, 6), 6, 5, "3")); if (verbose) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string console.log(cli.mergeTree.toString()); for (let clientId = 0; clientId < 4; clientId++) { for (let refSeq = 0; refSeq < 7; refSeq++) { diff --git a/packages/dds/sequence/src/test/testFarm.ts b/packages/dds/sequence/src/test/testFarm.ts deleted file mode 100644 index 02d26d0a7517..000000000000 --- a/packages/dds/sequence/src/test/testFarm.ts +++ /dev/null @@ -1,1834 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -// TODO: Some of these should be fixed -/* eslint-disable no-bitwise */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable guard-for-in */ -/* eslint-disable @typescript-eslint/no-base-to-string */ -/* eslint-disable @typescript-eslint/no-for-in-array */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ - -import path from "path"; -import { Trace } from "@fluid-internal/client-utils"; -import { assert } from "@fluidframework/core-utils"; -// eslint-disable-next-line import/no-internal-modules -import * as MergeTree from "@fluidframework/merge-tree/dist/test/"; -import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import JsDiff from "diff"; -import random from "random-js"; -import { createIntervalIndex } from "../intervalCollection"; -import { IntervalTree } from "../intervalTree"; -import { SequenceInterval, IntervalType, Interval } from "../intervals"; - -const clock = () => Trace.start(); - -const elapsedMicroseconds = (trace: Trace) => { - return trace.trace().duration * 1000; -}; - -// Enum AsyncRoundState { -// Insert, -// Remove, -// Tail -// } - -// interface AsyncRoundInfo { -// clientIndex: number; -// state: AsyncRoundState; -// insertSegmentCount?: number; -// removeSegmentCount?: number; -// iterIndex: number; -// } - -export function propertyCopy() { - const propCount = 2000; - const iterCount = 10000; - const a = []; - const v = []; - for (let i = 0; i < propCount; i++) { - a[i] = `prop${i}`; - v[i] = i; - } - let clockStart = clock(); - let obj: MergeTree.MapLike = MergeTree.createMap(); - for (let j = 0; j < iterCount; j++) { - obj = MergeTree.createMap(); - for (let i = 0; i < propCount; i++) { - obj[a[i]] = v[i]; - } - } - let et = elapsedMicroseconds(clockStart); - let perIter = (et / iterCount).toFixed(3); - let perProp = (et / (iterCount * propCount)).toFixed(3); - console.log(`arr prop init time ${perIter} per init; ${perProp} per property`); - clockStart = clock(); - for (let j = 0; j < iterCount; j++) { - const bObj = MergeTree.createMap(); - for (const key in obj) { - bObj[key] = obj[key]; - } - } - et = elapsedMicroseconds(clockStart); - perIter = (et / iterCount).toFixed(3); - perProp = (et / (iterCount * propCount)).toFixed(3); - console.log(`obj prop init time ${perIter} per init; ${perProp} per property`); -} - -function makeBookmarks(client: MergeTree.TestClient, bookmarkCount: number) { - const mt = random.engines.mt19937(); - mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - const bookmarks = []; - const len = client.mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.NonCollabClient, - ); - const maxRangeLen = Math.min(Math.floor(len / 100), 30); - for (let i = 0; i < bookmarkCount; i++) { - let pos1 = random.integer(0, len - 1)(mt); - const rangeLen = random.integer(0, maxRangeLen)(mt); - let pos2 = pos1 + rangeLen; - if (pos2 >= len) { - pos2 = len - 2; - } - if (pos1 > pos2) { - const temp = pos1; - pos1 = pos2; - pos2 = temp; - } - const segoff1 = client.getContainingSegment(pos1); - const segoff2 = client.getContainingSegment(pos2); - - if (segoff1?.segment && segoff2?.segment) { - const baseSegment1 = segoff1.segment; - const baseSegment2 = segoff2.segment; - const lref1 = client.createLocalReferencePosition( - baseSegment1, - segoff1.offset, - MergeTree.ReferenceType.RangeBegin, - undefined, - ); - const lref2 = client.createLocalReferencePosition( - baseSegment2, - segoff2.offset, - MergeTree.ReferenceType.RangeEnd, - undefined, - ); - lref1.addProperties({ [MergeTree.reservedRangeLabelsKey]: ["bookmark"] }); - lref2.addProperties({ [MergeTree.reservedRangeLabelsKey]: ["bookmark"] }); - bookmarks.push(new SequenceInterval(client, lref1, lref2, IntervalType.Simple)); - } else { - i--; - } - } - return bookmarks; -} - -function makeReferences(client: MergeTree.TestClient, referenceCount: number) { - const mt = random.engines.mt19937(); - mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - const refs = []; - const len = client.mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.NonCollabClient, - ); - for (let i = 0; i < referenceCount; i++) { - const pos = random.integer(0, len - 1)(mt); - const segoff = client.getContainingSegment(pos); - if (segoff?.segment) { - const baseSegment = segoff.segment; - const lref = client.createLocalReferencePosition( - baseSegment, - segoff.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - if (i & 1) { - lref.refType = MergeTree.ReferenceType.SlideOnRemove; - } - refs.push(lref); - } else { - i--; - } - } - return refs; -} - -export function TestPack(verbose = true) { - const mt = random.engines.mt19937(); - mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - const smallSegmentCountDistribution = random.integer(1, 4); - const randSmallSegmentCount = () => smallSegmentCountDistribution(mt); - const textLengthDistribution = random.integer(1, 5); - const randTextLength = () => textLengthDistribution(mt); - const zedCode = 48; - function randomString(len: number, c: string) { - let str = ""; - for (let i = 0; i < len; i++) { - str += c; - } - return str; - } - - const checkIncr = false; - - // Let incrGetTextTime = 0; - // let incrGetTextCalls = 0; - // let catchUpTime = 0; - // let catchUps = 0; - - function reportTiming(client: MergeTree.TestClient) { - if (!verbose) { - return; - } - const aveTime = (client.accumTime / client.accumOps).toFixed(1); - const stats = MergeTree.getStats(client.mergeTree); - const packTime = stats.packTime; - const ordTime = stats.ordTime; - const aveOrdTime = ((ordTime ?? 0) / client.accumOps).toFixed(1); - const avePackTime = ((packTime ?? 0) / client.accumOps).toFixed(1); - const aveExtraWindowTime = (client.accumWindowTime / client.accumOps).toFixed(1); - const aveWindow = (client.accumWindow / client.accumOps).toFixed(1); - - console.log(`ord time average: ${aveOrdTime}us max ${stats.maxOrdTime}us`); - console.log( - `${client.longClientId} accum time ${client.accumTime} us ops: ${client.accumOps} ave time ${aveTime} - pack ${avePackTime} ave window ${aveWindow}`, - ); - console.log( - `${client.longClientId} accum window time ${client.accumWindowTime} us ave window time not in ops ${aveExtraWindowTime}; max ${client.maxWindowTime}`, - ); - } - - function manyMergeTrees() { - const mergeTreeCount = 2000000; - const a = Array(mergeTreeCount); - for (let i = 0; i < mergeTreeCount; i++) { - a[i] = new MergeTree.MergeTree(); - } - for (;;) {} - } - - function clientServer(startFile?: string, initRounds = 1000) { - const clientCount = 5; - const fileSegCount = 0; - const initString = "don't ask for whom the bell tolls; it tolls for thee"; - let snapInProgress = false; - const asyncExec = false; - const addSnapClient = false; - const extractSnap = false; - const includeMarkers = false; - const measureBookmarks = true; - let bookmarks: SequenceInterval[]; - const bookmarkRangeTree = new IntervalTree(); - const testOrdinals = true; - let ordErrors = 0; - let ordSuccess = 0; - const measureRanges = true; - const referenceCount = 2000; - const bookmarkCount = 5000; - let references: MergeTree.LocalReferencePosition[]; - let refReads = 0; - let refReadTime = 0; - let posContextChecks = 0; - let posContextTime = 0; - let posContextResults = 0; - let rangeOverlapTime = 0; - let rangeOverlapChecks = 0; - let overlapIntervalResults = 0; - const testSyncload = false; - let snapClient: MergeTree.TestClient; - const useGroupOperationsForMoveWord = false; - let annotateProps: MergeTree.PropertySet | undefined; - const insertAsRefPos = false; - - const testServer = new MergeTree.TestServer({}); - testServer.measureOps = true; - if (startFile) { - MergeTree.loadTextFromFile(startFile, testServer.mergeTree, fileSegCount); - } else { - testServer.insertTextLocal(0, initString); - } - - const clients = new Array(clientCount); - for (let i = 0; i < clientCount; i++) { - clients[i] = new MergeTree.TestClient(); - clients[i].measureOps = true; - if (startFile) { - MergeTree.loadTextFromFile(startFile, clients[i].mergeTree, fileSegCount); - } else { - clients[i].insertTextLocal(0, initString); - } - if (annotateProps) { - clients[i].annotateRangeLocal(0, clients[i].getLength(), annotateProps, undefined); - } - clients[i].startOrUpdateCollaboration(`Fred${i}`); - } - testServer.startOrUpdateCollaboration("theServer"); - testServer.addClients(clients); - if (measureBookmarks) { - references = makeReferences(testServer, referenceCount); - if (measureRanges) { - bookmarks = makeBookmarks(testServer, bookmarkCount); - for (const bookmark of bookmarks) { - bookmarkRangeTree.put(bookmark); - } - } - } - if (testSyncload) { - const clockStart = clock(); - // Let segs = Paparazzo.Snapshot.loadSync("snap-initial"); - console.log(`sync load time ${elapsedMicroseconds(clockStart)}`); - const fromLoad = new MergeTree.MergeTree(); - // FromLoad.reloadFromSegments(segs); - const fromLoadText = new MergeTree.MergeTreeTextHelper(fromLoad).getText( - MergeTree.UniversalSequenceNumber, - MergeTree.NonCollabClient, - ); - const serverText = testServer.getText(); - if (fromLoadText !== serverText) { - console.log("snap file vs. text file mismatch"); - } - } - if (addSnapClient) { - snapClient = new MergeTree.TestClient(); - if (startFile) { - MergeTree.loadTextFromFile(startFile, snapClient.mergeTree, fileSegCount); - } else { - snapClient.insertTextLocal(0, initString); - } - snapClient.startOrUpdateCollaboration("snapshot"); - testServer.addListeners([snapClient]); - } - - function checkTextMatch() { - // Console.log(`checking text match @${server.getCurrentSeq()}`); - const serverText = testServer.getText(); - if (checkIncr) { - const serverIncrText = testServer.incrementalGetText(); - // IncrGetTextTime += elapsedMicroseconds(clockStart); - // incrGetTextCalls++; - if (serverIncrText !== serverText) { - console.log("incr get text mismatch"); - } - } - for (const client of clients) { - const cliText = client.getText(); - if (cliText !== serverText) { - console.log( - `mismatch @${testServer.getCurrentSeq()} client @${client.getCurrentSeq()} id: ${client.getClientId()}`, - ); - // Console.log(serverText); - // console.log(cliText); - const diffParts = JsDiff.diffChars(serverText, cliText); - for (const diffPart of diffParts) { - let annotes = ""; - if (diffPart.added) { - annotes += "added "; - } else if (diffPart.removed) { - annotes += "removed "; - } - if (diffPart.count) { - annotes += `count: ${diffPart.count}`; - } - console.log(`text: ${diffPart.value} ${annotes}`); - } - console.log(testServer.mergeTree.toString()); - console.log(client.mergeTree.toString()); - return true; - } - } - return false; - } - - const rounds = initRounds; - - function clientProcessSome(client: MergeTree.TestClient, all = false) { - const cliMsgCount = client.getMessageCount(); - const countToApply: number = all - ? cliMsgCount - : random.integer(Math.floor((2 * cliMsgCount) / 3), cliMsgCount)(mt); - client.applyMessages(countToApply); - } - - function serverProcessSome(server: MergeTree.TestClient, all = false) { - const svrMsgCount = server.getMessageCount(); - const countToApply: number = all - ? svrMsgCount - : random.integer(Math.floor((2 * svrMsgCount) / 3), svrMsgCount)(mt); - return server.applyMessages(countToApply); - } - - function randomSpateOfInserts(client: MergeTree.TestClient, charIndex: number) { - const textLen = randTextLength(); - const text = randomString( - textLen, - String.fromCharCode(zedCode + ((client.getCurrentSeq() + charIndex) % 50)), - ); - const preLen = client.getLength(); - const pos = random.integer(0, preLen)(mt); - if (includeMarkers) { - const markerOp = client.insertMarkerLocal(pos, MergeTree.ReferenceType.Tile, { - [MergeTree.reservedTileLabelsKey]: "test", - }); - testServer.enqueueMsg( - client.makeOpMessage(markerOp, MergeTree.UnassignedSequenceNumber), - ); - } - const textOp = client.insertTextLocal(pos, text); - testServer.enqueueMsg(client.makeOpMessage(textOp, MergeTree.UnassignedSequenceNumber)); - if (MergeTree.TestClient.useCheckQ) { - client.enqueueTestString(); - } - } - - function randomSpateOfRemoves(client: MergeTree.TestClient) { - const dlen = randTextLength(); - const preLen = client.getLength(); - const pos = random.integer(0, preLen)(mt); - const op = client.removeRangeLocal(pos, pos + dlen); - testServer.enqueueMsg(client.makeOpMessage(op)); - if (MergeTree.TestClient.useCheckQ) { - client.enqueueTestString(); - } - } - - function randomWordMove(client: MergeTree.TestClient) { - const word1 = client.findRandomWord(); - if (word1) { - const removeStart = word1.pos; - const removeEnd = removeStart + word1.text.length; - const ops: MergeTree.IMergeTreeDeltaOp[] = []; - const removeOp = client.removeRangeLocal(removeStart, removeEnd); - if (!useGroupOperationsForMoveWord) { - testServer.enqueueMsg(client.makeOpMessage(removeOp)); - if (MergeTree.TestClient.useCheckQ) { - client.enqueueTestString(); - } - } else if (removeOp) { - ops.push(removeOp); - } - - let word2 = client.findRandomWord(); - while (!word2) { - word2 = client.findRandomWord(); - } - const pos = word2.pos + word2.text.length; - - const segOff = client.getContainingSegment(pos); - const insertOp = - !insertAsRefPos && segOff.segment - ? client.insertAtReferencePositionLocal( - client.createLocalReferencePosition( - segOff.segment, - segOff.offset, - MergeTree.ReferenceType.Transient, - undefined, - ), - MergeTree.TextSegment.make(word1.text), - ) - : client.insertTextLocal(pos, word1.text); - - if (!useGroupOperationsForMoveWord) { - testServer.enqueueMsg(client.makeOpMessage(insertOp)); - if (MergeTree.TestClient.useCheckQ) { - client.enqueueTestString(); - } - } else if (insertOp) { - ops.push(insertOp); - } - - if (annotateProps) { - const annotateOp = client.annotateRangeLocal( - pos, - pos + word1.text.length, - annotateProps, - undefined, - ); - if (!useGroupOperationsForMoveWord) { - testServer.enqueueMsg(client.makeOpMessage(annotateOp)); - } else if (annotateOp) { - ops.push(annotateOp); - } - } - - if (useGroupOperationsForMoveWord) { - testServer.enqueueMsg(client.makeOpMessage(MergeTree.createGroupOp(...ops))); - if (MergeTree.TestClient.useCheckQ) { - client.enqueueTestString(); - } - } - } - } - - let errorCount = 0; - - // Function asyncRoundStep(asyncInfo: AsyncRoundInfo, roundCount: number) { - // if (asyncInfo.state == AsyncRoundState.Insert) { - // if (!asyncInfo.insertSegmentCount) { - // asyncInfo.insertSegmentCount = randSmallSegmentCount(); - // } - // if (asyncInfo.clientIndex == clients.length) { - // asyncInfo.state = AsyncRoundState.Remove; - // asyncInfo.iterIndex = 0; - // } - // else { - // let client = clients[asyncInfo.clientIndex]; - // if (startFile) { - // randomWordMove(client); - // } - // else { - // randomSpateOfInserts(client, asyncInfo.iterIndex); - // } - // asyncInfo.iterIndex++; - // if (asyncInfo.iterIndex == asyncInfo.insertSegmentCount) { - // asyncInfo.clientIndex++; - // asyncInfo.insertSegmentCount = undefined; - // asyncInfo.iterIndex = 0; - // } - // } - // } - // if (asyncInfo.state == AsyncRoundState.Remove) { - // if (!asyncInfo.removeSegmentCount) { - // asyncInfo.removeSegmentCount = Math.floor(3 * asyncInfo.insertSegmentCount / 4); - // if (asyncInfo.removeSegmentCount < 1) { - // asyncInfo.removeSegmentCount = 1; - // } - // } - // if (asyncInfo.clientIndex == clients.length) { - // asyncInfo.state = AsyncRoundState.Tail; - // } - // else { - // let client = clients[asyncInfo.clientIndex]; - // if (startFile) { - // randomWordMove(client); - // } - // else { - // randomSpateOfInserts(client, asyncInfo.iterIndex); - // } - // asyncInfo.iterIndex++; - // if (asyncInfo.iterIndex == asyncInfo.removeSegmentCount) { - // asyncInfo.clientIndex++; - // asyncInfo.removeSegmentCount = undefined; - // asyncInfo.iterIndex = 0; - // } - // } - // } - // if (asyncInfo.state == AsyncRoundState.Tail) { - // finishRound(roundCount); - // } - // else { - // setImmediate(asyncRoundStep, asyncInfo, roundCount); - // } - // } - - // function asyncRound(roundCount: number) { - // let asyncInfo = { - // clientIndex: 0, - // iterIndex: 0, - // state: AsyncRoundState.Insert - // } - // setImmediate(asyncRoundStep, asyncInfo, roundCount); - // } - - let extractSnapTime = 0; - let extractSnapOps = 0; - function finishRound(roundCount: number) { - // Process remaining messages - if (serverProcessSome(testServer, true)) { - return; - } - for (const client of clients) { - clientProcessSome(client, true); - } - - if (measureBookmarks) { - const refReadsPerRound = 200; - const posChecksPerRound = 200; - const rangeChecksPerRound = 200; - let clockStart = clock(); - for (let i = 0; i < refReadsPerRound; i++) { - testServer.localReferencePositionToPosition(references[i]); - refReads++; - } - refReadTime += elapsedMicroseconds(clockStart); - if (testOrdinals) { - const mt2 = random.engines.mt19937(); - mt2.seedWithArray([0xdeadbeef, 0xfeedbed]); - const checkRange = []; - const len = testServer.mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - testServer.getClientId(), - ); - for (let i = 0; i < rangeChecksPerRound; i++) { - const e = random.integer(0, len - 2)(mt2); - const rangeSize = random.integer(1, Math.min(1000, len - 2))(mt2); - let b = e - rangeSize; - if (b < 0) { - b = 0; - } - checkRange[i] = [b, b + rangeSize]; - const segoff1 = testServer.getContainingSegment(checkRange[i][0]); - const segoff2 = testServer.getContainingSegment(checkRange[i][1]); - if (segoff1 && segoff2 && segoff1.segment && segoff2.segment) { - // Console.log(`[${checkRange[i][0]},${checkRange[i][1]})`); - if (segoff1.segment === segoff2.segment) { - // Console.log("same segment"); - } else if (segoff1.segment.ordinal > segoff2.segment.ordinal) { - ordErrors++; - console.log( - `reverse ordinals ${ordinalToArray( - segoff1.segment.ordinal, - )} > ${ordinalToArray(segoff2.segment.ordinal)}`, - ); - console.log( - `segments ${segoff1.segment.toString()} ${segoff2.segment.toString()}`, - ); - console.log(testServer.mergeTree.toString()); - break; - } else { - ordSuccess++; - // Console.log(`happy ordinals ${MergeTree.ordinalToArray(segoff1.segment.ordinal)} < ${MergeTree.ordinalToArray(segoff2.segment.ordinal)}`); - } - } else { - // Console.log(`no seg for [${b},${e}) with len ${len}`); - } - } - } - if (measureRanges) { - const mt2 = random.engines.mt19937(); - mt2.seedWithArray([0xdeadbeef, 0xfeedbed]); - const len = testServer.mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - testServer.getClientId(), - ); - const checkPos = []; - const checkRange = []; - const checkPosRanges = []; - const checkRangeRanges = []; - for (let i = 0; i < posChecksPerRound; i++) { - checkPos[i] = random.integer(0, len - 2)(mt2); - const segoff1 = testServer.getContainingSegment(checkPos[i]); - const segoff2 = testServer.getContainingSegment(checkPos[i] + 1); - if (segoff1?.segment && segoff2?.segment) { - const lrefPos1 = testServer.createLocalReferencePosition( - segoff1.segment, - segoff1.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - const lrefPos2 = testServer.createLocalReferencePosition( - segoff2.segment, - segoff2.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - checkPosRanges[i] = new SequenceInterval( - testServer, - lrefPos1, - lrefPos2, - IntervalType.Simple, - ); - } else { - i--; - } - } - for (let i = 0; i < rangeChecksPerRound; i++) { - const e = random.integer(0, len - 2)(mt2); - const rangeSize = random.integer(1, Math.min(1000, len - 2))(mt2); - let b = e - rangeSize; - if (b < 0) { - b = 0; - } - checkRange[i] = [b, b + rangeSize]; - const segoff1 = testServer.getContainingSegment(checkRange[i][0]); - const segoff2 = testServer.getContainingSegment(checkRange[i][1]); - if (segoff1?.segment && segoff2?.segment) { - const lrefPos1 = testServer.createLocalReferencePosition( - segoff1.segment, - segoff1.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - const lrefPos2 = testServer.createLocalReferencePosition( - segoff2.segment, - segoff2.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - checkRangeRanges[i] = new SequenceInterval( - testServer, - lrefPos1, - lrefPos2, - IntervalType.Simple, - ); - } else { - i--; - } - } - const showResults = false; - clockStart = clock(); - - for (let i = 0; i < posChecksPerRound; i++) { - const ivals = bookmarkRangeTree.match(checkPosRanges[i]); - if (showResults) { - console.log(`results for point [${checkPos[i]},${checkPos[i] + 1})`); - for (const ival of ivals) { - const pos1 = testServer.mergeTree.referencePositionToLocalPosition( - ival.key.start, - ); - const pos2 = testServer.mergeTree.referencePositionToLocalPosition( - ival.key.end, - ); - console.log(`[${pos1},${pos2})`); - } - } - posContextResults += ivals.length; - } - posContextTime += elapsedMicroseconds(clockStart); - posContextChecks += posChecksPerRound; - - clockStart = clock(); - for (let i = 0; i < rangeChecksPerRound; i++) { - const ivals = bookmarkRangeTree.match(checkRangeRanges[i]); - if (showResults) { - console.log(`results for [${checkRange[i][0]},${checkRange[i][1]})`); - for (const ival of ivals) { - const pos1 = testServer.mergeTree.referencePositionToLocalPosition( - ival.key.start, - ); - const pos2 = testServer.mergeTree.referencePositionToLocalPosition( - ival.key.end, - ); - console.log(`[${pos1},${pos2})`); - } - } - overlapIntervalResults += ivals.length; - } - rangeOverlapTime += elapsedMicroseconds(clockStart); - rangeOverlapChecks += rangeChecksPerRound; - } - } - - if (extractSnap) { - const clockStart = clock(); - // Let snapshot = new Paparazzo.Snapshot(snapClient.mergeTree); - // snapshot.extractSync(); - extractSnapTime += elapsedMicroseconds(clockStart); - extractSnapOps++; - } - /* - If (checkTextMatch()) { - console.log(`round: ${i}`); - break; - } - */ - // console.log(server.getText()); - // console.log(server.mergeTree.toString()); - // console.log(MergeTree.getStats(server.mergeTree)); - if (0 === roundCount % 100) { - const clockStart = clock(); - if (checkTextMatch()) { - console.log(`round: ${roundCount} BREAK`); - errorCount++; - return errorCount; - } - checkTime += elapsedMicroseconds(clockStart); - if (verbose) { - console.log(`wall clock is ${((Date.now() - startTime) / 1000.0).toFixed(1)}`); - } - const stats = MergeTree.getStats(testServer.mergeTree); - const liveAve = (stats.liveCount / stats.nodeCount).toFixed(1); - const posLeaves = stats.leafCount - stats.removedLeafCount; - let aveExtractSnapTime = "off"; - if (extractSnapOps > 0) { - aveExtractSnapTime = (extractSnapTime / extractSnapOps).toFixed(1); - } - console.log( - `round: ${roundCount} seq ${ - testServer.seq - } char count ${testServer.getLength()} height ${stats.maxHeight} lv ${ - stats.leafCount - } rml ${stats.removedLeafCount} p ${posLeaves} nodes ${ - stats.nodeCount - } pop ${liveAve} histo ${stats.histo}`, - ); - if (extractSnapOps > 0) { - aveExtractSnapTime = (extractSnapTime / extractSnapOps).toFixed(1); - console.log(`ave extract snap time ${aveExtractSnapTime}`); - } - reportTiming(testServer); - if (measureBookmarks) { - const timePerRead = (refReadTime / refReads).toFixed(2); - const bookmarksPerSeg = (bookmarkCount / stats.leafCount).toFixed(2); - if (ordErrors > 0) { - console.log(`ord errors: ${ordErrors}`); - } - if (ordSuccess > 0) { - console.log(`total ord range tests ${ordSuccess}`); - } - console.log( - `bookmark count ${bookmarkCount} ave. per seg ${bookmarksPerSeg} time/read ${timePerRead}`, - ); - if (measureRanges) { - const timePerContextCheck = (posContextTime / posContextChecks).toFixed(2); - const results = (posContextResults / posContextChecks).toFixed(2); - console.log( - `ave. per bookmark context check ${timePerContextCheck} ave results per check ${results}`, - ); - const timePerRangeCheck = (rangeOverlapTime / rangeOverlapChecks).toFixed( - 2, - ); - const resultsRange = (overlapIntervalResults / rangeOverlapChecks).toFixed( - 2, - ); - console.log( - `ave. per bookmark range check ${timePerRangeCheck} ave results per check ${resultsRange}`, - ); - } - } - reportTiming(clients[2]); - let totalTime = testServer.accumTime + testServer.accumWindowTime; - for (const client of clients) { - totalTime += client.accumTime + client.accumWindowTime; - } - if (verbose) { - console.log( - `total time ${(totalTime / 1000000.0).toFixed(1)} check time ${( - checkTime / 1000000.0 - ).toFixed(1)}`, - ); - } - // Console.log(server.getText()); - // console.log(server.mergeTree.toString()); - } - return errorCount; - } - - function round(roundCount: number) { - for (const client of clients) { - const insertSegmentCount = randSmallSegmentCount(); - for (let j = 0; j < insertSegmentCount; j++) { - if (startFile) { - randomWordMove(client); - } else { - randomSpateOfInserts(client, j); - } - } - if (serverProcessSome(testServer)) { - return; - } - clientProcessSome(client); - - let removeSegmentCount = Math.floor((3 * insertSegmentCount) / 4); - if (removeSegmentCount < 1) { - removeSegmentCount = 1; - } - for (let j = 0; j < removeSegmentCount; j++) { - if (startFile) { - randomWordMove(client); - } else { - randomSpateOfRemoves(client); - if (includeMarkers) { - if (client.getLength() > 200) { - randomSpateOfRemoves(client); - } - } - } - } - if (serverProcessSome(testServer)) { - return; - } - clientProcessSome(client); - } - finishRound(roundCount); - } - - const startTime = Date.now(); - let checkTime = 0; - let asyncRoundCount = 0; - let lastSnap = 0; - // Let checkSnapText = true; - - // function snapFinished() { - // snapInProgress = false; - // let curmin = snapClient.mergeTree.getCollabWindow().minSeq; - // console.log(`snap finished round ${asyncRoundCount} server seq ${server.getCurrentSeq()} seq ${snapClient.getCurrentSeq()} minseq ${curmin}`); - // let clockStart = clock(); - // //snapClient.verboseOps = true; - // clientProcessSome(snapClient, true); - // catchUpTime += elapsedMicroseconds(clockStart); - // catchUps++; - // if (checkSnapText) { - // let serverText = server.getText(); - // let snapText = snapClient.getText(); - // if (serverText != snapText) { - // console.log(`mismatch @${server.getCurrentSeq()} client @${snapClient.getCurrentSeq()} id: ${snapClient.getClientId()}`); - // } - // } - // } - - function ohSnap(filename: string) { - snapInProgress = true; - const curmin = snapClient.getCollabWindow().minSeq; - lastSnap = curmin; - console.log(`snap started seq ${snapClient.getCurrentSeq()} minseq ${curmin}`); - // Let snapshot = new Paparazzo.Snapshot(snapClient.mergeTree, filename, snapFinished); - // snapshot.start(); - } - - function asyncStep() { - round(asyncRoundCount); - const curmin = testServer.getCollabWindow().minSeq; - if (!snapInProgress && lastSnap < curmin) { - ohSnap("snapit"); - } - asyncRoundCount++; - if (asyncRoundCount < rounds) { - setImmediate(asyncStep); - } - } - - if (asyncExec) { - ohSnap("snap-initial"); - setImmediate(asyncStep); - } else { - for (let i = 0; i < rounds; i++) { - round(i); - if (errorCount > 0) { - break; - } - } - tail(); - } - function tail() { - reportTiming(testServer); - reportTiming(clients[2]); - // Console.log(server.getText()); - // console.log(server.mergeTree.toString()); - } - return errorCount; - } - - const clientNames = ["Ed", "Ted", "Ned", "Harv", "Marv", "Glenda", "Susan"]; - - function firstTest() { - let cli = new MergeTree.TestClient(); - cli.insertTextLocal(0, "on the mat."); - cli.startOrUpdateCollaboration("Fred1"); - for (const cname of clientNames) { - cli.addLongClientId(cname); - } - cli.insertTextRemote(0, "that ", undefined, 1, 0, "1"); - if (verbose) { - console.log(cli.mergeTree.toString()); - } - cli.insertTextRemote(0, "fat ", undefined, 2, 0, "2"); - if (verbose) { - console.log(cli.mergeTree.toString()); - } - cli.insertTextLocal(5, "cat "); - if (verbose) { - console.log(cli.mergeTree.toString()); - } - if (verbose) { - for (let i = 0; i < 4; i++) { - for (let j = 0; j < 3; j++) { - console.log(cli.relText(i, j)); - } - } - } - cli.mergeTree.ackPendingSegment({ - op: { type: MergeTree.MergeTreeDeltaType.INSERT }, - sequencedMessage: { - sequenceNumber: 3, - } as ISequencedDocumentMessage, - }); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 4; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - cli.insertTextRemote(6, "very ", undefined, 4, 2, "2"); - cli.insertMarkerRemote( - 0, - { refType: MergeTree.ReferenceType.Tile }, - { [MergeTree.reservedTileLabelsKey]: ["peach"] }, - 5, - 0, - "2", - ); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 5; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - - cli = new MergeTree.TestClient(); - cli.insertTextLocal(0, " old sock!"); - cli.startOrUpdateCollaboration("Fred2"); - for (const cname of clientNames) { - cli.addLongClientId(cname); - } - cli.insertTextRemote(0, "abcde", undefined, 1, 0, "2"); - const segoff = cli.getContainingSegment(0); - const lref1 = cli.createLocalReferencePosition( - segoff.segment, - segoff.offset, - MergeTree.ReferenceType.Simple, - undefined, - ); - cli.insertTextRemote(0, "yyy", undefined, 2, 0, "1"); - cli.insertTextRemote(2, "zzz", undefined, 3, 1, "3"); - cli.insertTextRemote(1, "EAGLE", undefined, 4, 1, "4"); - cli.insertTextRemote(4, "HAS", undefined, 5, 1, "5"); - cli.insertTextLocal(19, " LANDED"); - cli.insertTextRemote(0, "yowza: ", undefined, 6, 4, "2"); - const lref1pos = cli.mergeTree.referencePositionToLocalPosition(lref1); - console.log(`lref pos: ${lref1pos}`); - cli.mergeTree.ackPendingSegment({ - op: { type: MergeTree.MergeTreeDeltaType.INSERT }, - sequencedMessage: { - sequenceNumber: 7, - } as ISequencedDocumentMessage, - }); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 6; clientId++) { - for (let refSeq = 0; refSeq < 8; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - cli.applyMsg(cli.makeOpMessage(MergeTree.createRemoveRangeOp(3, 5), 8, 6, "1")); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 6; clientId++) { - for (let refSeq = 0; refSeq < 9; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - cli = new MergeTree.TestClient(); - cli.insertTextLocal(0, "abcdefgh"); - cli.startOrUpdateCollaboration("Fred3"); - for (const cname of clientNames) { - cli.addLongClientId(cname); - } - cli.applyMsg(cli.makeOpMessage(MergeTree.createRemoveRangeOp(1, 3), 1, 0, "3")); - if (verbose) { - console.log(cli.mergeTree.toString()); - } - cli.insertTextRemote(2, "zzz", undefined, 2, 0, "2"); - if (verbose) { - console.log(cli.mergeTree.toString()); - } - cli.insertTextRemote(9, " chaser", undefined, 3, 2, "3"); - cli.removeRangeLocal(12, 14); - cli.mergeTree.ackPendingSegment({ - op: { type: MergeTree.MergeTreeDeltaType.REMOVE }, - sequencedMessage: { - sequenceNumber: 4, - } as ISequencedDocumentMessage, - }); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 5; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - cli.insertTextLocal(14, "*yolumba*"); - cli.insertTextLocal(17, "-zanzibar-"); - cli.mergeTree.ackPendingSegment({ - op: { type: MergeTree.MergeTreeDeltaType.INSERT }, - sequencedMessage: { - sequenceNumber: 5, - } as ISequencedDocumentMessage, - }); - cli.insertTextRemote(2, "(aaa)", undefined, 6, 4, "2"); - cli.mergeTree.ackPendingSegment({ - op: { type: MergeTree.MergeTreeDeltaType.INSERT }, - sequencedMessage: { - sequenceNumber: 7, - } as ISequencedDocumentMessage, - }); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 8; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - /* - Cli.removeRangeLocal(3,8); - cli.removeRangeLocal(5,7); - cli.ackPendingSegment(8); - cli.ackPendingSegment(9); - */ - cli.applyMsg(cli.makeOpMessage(MergeTree.createRemoveRangeOp(3, 8), 8, 7, "2")); - cli.applyMsg(cli.makeOpMessage(MergeTree.createRemoveRangeOp(5, 7), 9, 7, "2")); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 10; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - const localRemoveOp = cli.removeRangeLocal(3, 5); - cli.applyMsg(cli.makeOpMessage(MergeTree.createRemoveRangeOp(3, 6), 10, 9, "2")); - cli.applyMsg(cli.makeOpMessage(localRemoveOp, 11)); - if (verbose) { - console.log(cli.mergeTree.toString()); - for (let clientId = 0; clientId < 4; clientId++) { - for (let refSeq = 0; refSeq < 12; refSeq++) { - console.log(cli.relText(clientId, refSeq)); - } - } - } - } - - return { - firstTest, - clientServer, - manyMergeTrees, - }; -} - -const editFlat = (source: string, s: number, dl: number, nt = "") => - source.substring(0, s) + nt + source.substring(s + dl, source.length); - -let accumTime = 0; - -function checkInsertMergeTree( - mergeTree: MergeTree.MergeTree, - pos: number, - textSegment: MergeTree.TextSegment, - verbose = false, -) { - const helper = new MergeTree.MergeTreeTextHelper(mergeTree); - let checkText = helper.getText(MergeTree.UniversalSequenceNumber, MergeTree.LocalClientId); - checkText = editFlat(checkText, pos, 0, textSegment.text); - const clockStart = clock(); - mergeTree.insertSegments( - pos, - [new MergeTree.TextSegment(textSegment.text)], - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - MergeTree.UniversalSequenceNumber, - undefined, - ); - accumTime += elapsedMicroseconds(clockStart); - const updatedText = helper.getText(MergeTree.UniversalSequenceNumber, MergeTree.LocalClientId); - const result = checkText === updatedText; - if (!result && verbose) { - console.log(`mismatch(o): ${checkText}`); - console.log(`mismatch(u): ${updatedText}`); - } - return result; -} - -function checkMarkRemoveMergeTree( - mergeTree: MergeTree.MergeTree, - start: number, - end: number, - verbose = false, -) { - const helper = new MergeTree.MergeTreeTextHelper(mergeTree); - const origText = helper.getText(MergeTree.UniversalSequenceNumber, MergeTree.LocalClientId); - const checkText = editFlat(origText, start, end - start); - const clockStart = clock(); - mergeTree.markRangeRemoved( - start, - end, - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - MergeTree.UniversalSequenceNumber, - false, - // `opArgs` being `undefined` is special-cased specifically for internal - // test code - undefined as any, - ); - accumTime += elapsedMicroseconds(clockStart); - const updatedText = helper.getText(MergeTree.UniversalSequenceNumber, MergeTree.LocalClientId); - const result = checkText === updatedText; - if (!result && verbose) { - console.log(`mismatch(o): ${origText}`); - console.log(`mismatch(c): ${checkText}`); - console.log(`mismatch(u): ${updatedText}`); - } - return result; -} - -const makeCollabTextSegment = (text: string) => new MergeTree.TextSegment(text); - -export function mergeTreeCheckedTest() { - const mergeTree = new MergeTree.MergeTree(); - mergeTree.insertSegments( - 0, - [MergeTree.TextSegment.make("the cat is on the mat")], - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - MergeTree.UniversalSequenceNumber, - undefined, - ); - const insertCount = 2000; - const removeCount = 1400; - const largeRemoveCount = 20; - const mt = random.engines.mt19937(); - mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - const imin = 1; - const imax = 9; - const distribution = random.integer(imin, imax); - const largeDistribution = random.integer(10, 1000); - const randInt = () => distribution(mt); - const randLargeInt = () => largeDistribution(mt); - function randomString(len: number, c: string) { - let str = ""; - for (let i = 0; i < len; i++) { - str += c; - } - return str; - } - accumTime = 0; - let accumTreeSize = 0; - let treeCount = 0; - let errorCount = 0; - for (let i = 0; i < insertCount; i++) { - const slen = randInt(); - const s = randomString(slen, String.fromCharCode(48 + slen)); - const preLen = mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const pos = random.integer(0, preLen)(mt); - if (!checkInsertMergeTree(mergeTree, pos, makeCollabTextSegment(s), true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} slen: ${slen} s: ${s} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - if (i > 0 && 0 === i % 1000) { - const perIter = (accumTime / (i + 1)).toFixed(3); - treeCount++; - accumTreeSize += mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const averageTreeSize = (accumTreeSize / treeCount).toFixed(3); - console.log( - `i: ${i} time: ${accumTime}us which is average ${perIter} per insert with average tree size ${averageTreeSize}`, - ); - } - } - accumTime = 0; - accumTreeSize = 0; - treeCount = 0; - for (let i = 0; i < largeRemoveCount; i++) { - const dlen = randLargeInt(); - const preLen = mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const pos = random.integer(0, preLen)(mt); - // Console.log(itree.toString()); - if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - break; - } - if (i > 0 && 0 === i % 10) { - const perIter = (accumTime / (i + 1)).toFixed(3); - treeCount++; - accumTreeSize += mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const averageTreeSize = (accumTreeSize / treeCount).toFixed(3); - console.log( - `i: ${i} time: ${accumTime}us which is average ${perIter} per large del with average tree size ${averageTreeSize}`, - ); - } - } - accumTime = 0; - accumTreeSize = 0; - treeCount = 0; - for (let i = 0; i < removeCount; i++) { - const dlen = randInt(); - const preLen = mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const pos = random.integer(0, preLen)(mt); - // Console.log(itree.toString()); - if (i & 1) { - if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { - console.log( - `mr i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - } else { - if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - } - if (i > 0 && 0 === i % 1000) { - const perIter = (accumTime / (i + 1)).toFixed(3); - treeCount++; - accumTreeSize += mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const averageTreeSize = (accumTreeSize / treeCount).toFixed(3); - console.log( - `i: ${i} time: ${accumTime}us which is average ${perIter} per del with average tree size ${averageTreeSize}`, - ); - } - } - accumTime = 0; - accumTreeSize = 0; - treeCount = 0; - for (let i = 0; i < insertCount; i++) { - const slen = randInt(); - const s = randomString(slen, String.fromCharCode(48 + slen)); - const preLen = mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const pos = random.integer(0, preLen)(mt); - if (!checkInsertMergeTree(mergeTree, pos, makeCollabTextSegment(s), true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} slen: ${slen} s: ${s} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - if (i > 0 && 0 === i % 1000) { - const perIter = (accumTime / (i + 1)).toFixed(3); - treeCount++; - accumTreeSize += mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const averageTreeSize = (accumTreeSize / treeCount).toFixed(3); - console.log( - `i: ${i} time: ${accumTime}us which is average ${perIter} per insert with average tree size ${averageTreeSize}`, - ); - } - } - accumTime = 0; - accumTreeSize = 0; - treeCount = 0; - for (let i = 0; i < removeCount; i++) { - const dlen = randInt(); - const preLen = mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const pos = random.integer(0, preLen)(mt); - // Console.log(itree.toString()); - if (i & 1) { - if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - } else { - if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { - console.log( - `i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - )}`, - ); - console.log(mergeTree.toString()); - errorCount++; - break; - } - } - if (i > 0 && 0 === i % 1000) { - const perIter = (accumTime / (i + 1)).toFixed(3); - treeCount++; - accumTreeSize += mergeTree.getLength( - MergeTree.UniversalSequenceNumber, - MergeTree.LocalClientId, - ); - const averageTreeSize = (accumTreeSize / treeCount).toFixed(3); - console.log( - `i: ${i} time: ${accumTime}us which is average ${perIter} per del with average tree size ${averageTreeSize}`, - ); - } - } - return errorCount; -} - -export class RandomPack { - mt: Random.MT19937; - constructor() { - this.mt = random.engines.mt19937(); - this.mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - } - - randInteger(min: number, max: number) { - return random.integer(min, max)(this.mt); - } - - randString(wordCount: number) { - const exampleWords = [ - "giraffe", - "hut", - "aardvark", - "gold", - "hover", - "yurt", - "hot", - "antelope", - "gift", - "banana", - "book", - "airplane", - "kitten", - "moniker", - "lemma", - "doughnut", - "orange", - "tangerine", - ]; - let buf = ""; - for (let i = 0; i < wordCount; i++) { - const exampleWord = exampleWords[this.randInteger(0, exampleWords.length - 1)]; - if (i > 0) { - buf += " "; - } - buf += exampleWord; - } - return buf; - } -} - -function docNodeToString(docNode: DocumentNode) { - return typeof docNode === "string" ? docNode : docNode.name; -} - -export type DocumentNode = string | DocumentTree; -/** - * Generate and model documents from the following tree grammar: - * Row -\> row[Box*]; - * Box -\> box[Content]; - * Content -\> (Row|Paragraph)*; - * Paragraph -\> pgtile text; - * Document-\> Content - */ -export class DocumentTree { - pos = 0; - ids = { box: 0, row: 0 }; - id: string | undefined; - static randPack = new RandomPack(); - - constructor( - public name: string, - public children: DocumentNode[], - ) {} - - addToMergeTree(client: MergeTree.TestClient, docNode: DocumentNode) { - if (typeof docNode === "string") { - const text = docNode; - client.insertTextLocal(this.pos, text); - this.pos += text.length; - } else { - let id: number | undefined; - if (docNode.name === "pg") { - client.insertMarkerLocal(this.pos, MergeTree.ReferenceType.Tile, { - [MergeTree.reservedTileLabelsKey]: [docNode.name], - }); - this.pos++; - } else { - // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - const trid = docNode.name + this.ids[docNode.name].toString(); - docNode.id = trid; - id = this.ids[docNode.name]++; - const props = { - [MergeTree.reservedMarkerIdKey]: trid, - [MergeTree.reservedRangeLabelsKey]: [docNode.name], - }; - let behaviors = MergeTree.ReferenceType.NestBegin; - if (docNode.name === "row") { - props[MergeTree.reservedTileLabelsKey] = ["pg"]; - behaviors |= MergeTree.ReferenceType.Tile; - } - - client.insertMarkerLocal(this.pos, behaviors, props); - this.pos++; - } - for (const child of docNode.children) { - this.addToMergeTree(client, child); - } - if (docNode.name !== "pg") { - assert(id !== undefined, "expected `id` to be defined"); - const etrid = `end-${docNode.name}${id.toString()}`; - client.insertMarkerLocal(this.pos, MergeTree.ReferenceType.NestEnd, { - [MergeTree.reservedMarkerIdKey]: etrid, - [MergeTree.reservedRangeLabelsKey]: [docNode.name], - }); - this.pos++; - } - } - } - - checkStacksAllPositions(client: MergeTree.TestClient) { - let errorCount = 0; - let pos = 0; - const verbose = false; - const stacks = { - box: new MergeTree.Stack(), - row: new MergeTree.Stack(), - }; - - function printStack(stack: MergeTree.Stack) { - for (const item in stack.items) { - console.log(item); - } - } - - function printStacks() { - for (const name of ["box", "row"]) { - console.log(`${name}:`); - printStack(stacks[name]); - } - } - - function checkTreeStackEmpty(treeStack: MergeTree.Stack) { - if (!treeStack.empty()) { - errorCount++; - console.log("mismatch: client stack empty; tree stack not"); - } - } - - const checkNodeStacks = (docNode: DocumentNode) => { - if (typeof docNode === "string") { - const text = docNode; - const epos = pos + text.length; - if (verbose) { - console.log(`stacks for [${pos}, ${epos}): ${text}`); - printStacks(); - } - const cliStacks = client.getStackContext(pos, ["box", "row"]); - for (const name of ["box", "row"]) { - const cliStack = cliStacks[name]; - const treeStack = >stacks[name]; - if (cliStack) { - const len = cliStack.items.length; - if (len > 0) { - if (len !== treeStack.items.length) { - console.log( - `stack length mismatch cli ${len} tree ${treeStack.items.length}`, - ); - errorCount++; - } - for (let i = 0; i < len; i++) { - const cliMarkerId = (cliStack.items[i] as MergeTree.Marker).getId(); - const treeMarkerId = treeStack.items[i]; - if (cliMarkerId !== treeMarkerId) { - errorCount++; - console.log( - `mismatch index ${i}: ${cliMarkerId} !== ${treeMarkerId} pos ${pos} text ${text}`, - ); - printStack(treeStack); - console.log(client.mergeTree.toString()); - } - } - } else { - checkTreeStackEmpty(treeStack); - } - } else { - checkTreeStackEmpty(treeStack); - } - } - pos = epos; - } else { - pos++; - if (docNode.name === "pg") { - checkNodeStacks(docNode.children[0]); - } else { - stacks[docNode.name].push(docNode.id); - for (const child of docNode.children) { - checkNodeStacks(child); - } - stacks[docNode.name].pop(); - pos++; - } - } - }; - - let prevPos = -1; - let prevChild: DocumentNode | undefined; - - // Console.log(client.mergeTree.toString()); - for (const rootChild of this.children) { - if (prevPos >= 0) { - if (typeof prevChild !== "string" && prevChild?.name === "row") { - const id = prevChild.id; - const endId = `end-${id}`; - const endRowMarker = client.getMarkerFromId(endId); - const endRowPos = client.getPosition(endRowMarker); - prevPos = endRowPos; - } - const tilePos = client.findTile(prevPos, "pg", false); - if (tilePos) { - if (tilePos.pos !== pos) { - errorCount++; - console.log( - `next tile ${tilePos.tile} found from pos ${prevPos} at ${tilePos.pos} compare to ${pos}`, - ); - } - } - } - if (verbose) { - console.log(`next child ${pos} with name ${docNodeToString(rootChild)}`); - } - prevPos = pos; - prevChild = rootChild; - // PrintStacks(); - checkNodeStacks(rootChild); - } - return errorCount; - } - - private generateClient() { - const client = new MergeTree.TestClient(); - client.startOrUpdateCollaboration("Fred"); - for (const child of this.children) { - this.addToMergeTree(client, child); - } - return client; - } - - static test1() { - const doc = DocumentTree.generateDocument(); - const client = doc.generateClient(); - return doc.checkStacksAllPositions(client); - } - - static generateDocument() { - const tree = new DocumentTree("Document", DocumentTree.generateContent(0.6)); - return tree; - } - - static generateContent(initialRowProbability: number) { - let rowProbability = initialRowProbability; - const items = []; - const docLen = DocumentTree.randPack.randInteger(7, 25); - for (let i = 0; i < docLen; i++) { - const rowThreshold = rowProbability * 1000; - const selector = DocumentTree.randPack.randInteger(1, 1000); - if (selector >= rowThreshold) { - const pg = DocumentTree.generateParagraph(); - items.push(pg); - } else { - rowProbability /= 2; - if (rowProbability < 0.08) { - rowProbability = 0; - } - const row = DocumentTree.generateRow(rowProbability); - items.push(row); - } - } - return items; - } - - // Model pg tile as tree with single child - static generateParagraph() { - const wordCount = DocumentTree.randPack.randInteger(1, 6); - const text = DocumentTree.randPack.randString(wordCount); - const pgTree = new DocumentTree("pg", [text]); - return pgTree; - } - - static generateRow(rowProbability: number) { - const items = []; - const rowLen = DocumentTree.randPack.randInteger(1, 5); - for (let i = 0; i < rowLen; i++) { - const item = DocumentTree.generateBox(rowProbability); - items.push(item); - } - return new DocumentTree("row", items); - } - - static generateBox(rowProbability: number) { - return new DocumentTree("box", DocumentTree.generateContent(rowProbability)); - } -} - -export function intervalTest() { - const mt = random.engines.mt19937(); - mt.seedWithArray([0xdeadbeef, 0xfeedbed]); - const imin = 0; - const imax = 10000000; - const intCount = 50000; - const arr = [] as Interval[]; - const distribution = random.integer(imin, imax); - const randInt = () => distribution(mt); - const intervalIndex = createIntervalIndex(); - - for (let i = 0; i < intCount; i++) { - let a = randInt(); - let b = randInt(); - while (a === b) { - b = randInt(); - } - if (a > b) { - const temp = a; - a = b; - b = temp; - } - arr.push(intervalIndex.addInterval(a, b, IntervalType.Simple, { id: i })); - } - let dup = 0; - for (let i = 0; i < intCount; i++) { - if (arr[i].getAdditionalPropertySets()) { - dup++; - } - } - console.log(`dup: ${dup}`); -} - -export interface ICmd { - description?: string; - iconURL?: string; - exec?: () => void; -} -export function tstSimpleCmd() { - const tst = new MergeTree.TST(); - tst.put("zest", { description: "zesty" }); - tst.put("nest", { description: "nesty" }); - tst.put("newt", { description: "newty" }); - tst.put("neither", { description: "neithery" }); - tst.put("restitution", { description: "restitutiony" }); - tst.put("restful", { description: "restfuly" }); - tst.put("fish", { description: "fishy" }); - tst.put("nurf", { description: "nurfy" }); - tst.put("reify", { description: "reifyy" }); - tst.put("pert", { description: "perty" }); - tst.put("jest", { description: "jesty" }); - tst.put("jestcuz", { description: "jestcuzy" }); - let res = tst.pairsWithPrefix("je"); - console.log("trying je"); - for (const pair of res) { - console.log(`key: ${pair.key} val: ${pair.val.description}`); - } - res = tst.pairsWithPrefix("n"); - console.log("trying n"); - for (const pair of res) { - console.log(`key: ${pair.key} val: ${pair.val.description}`); - } - res = tst.pairsWithPrefix("ne"); - console.log("trying ne"); - for (const pair of res) { - console.log(`key: ${pair.key} val: ${pair.val.description}`); - } - res = []; - tst.map((key, val) => res.push({ key, val })); - console.log("trying map"); - for (const pair of res) { - console.log(`key: ${pair.key} val: ${pair.val.description}`); - } -} - -const testPropCopy = false; -const docTree = false; -const chktst = false; -const clientServerTest = true; -const tstTest = false; -const doFirstTest = false; -const ivalTest = false; - -if (doFirstTest) { - const testPack = TestPack(true); - testPack.firstTest(); -} - -if (ivalTest) { - intervalTest(); -} - -if (tstTest) { - tstSimpleCmd(); -} - -if (chktst) { - mergeTreeCheckedTest(); -} - -if (testPropCopy) { - propertyCopy(); -} - -if (docTree) { - DocumentTree.test1(); -} - -if (clientServerTest) { - const ppTest = true; - const testPack = TestPack(); - const baseDir = "../../../merge-tree/src/test/literature"; - const filename = path.join(__dirname, baseDir, "pp.txt"); - if (ppTest) { - testPack.clientServer(filename, 100000); - } else { - testPack.clientServer(undefined, 100000); - } -} - -function ordinalToArray(ord: string) { - const a: number[] = []; - if (ord) { - for (let i = 0, len = ord.length; i < len; i++) { - a.push(ord.charCodeAt(i)); - } - } - return a; -} diff --git a/packages/dds/sequence/src/test/tsconfig.json b/packages/dds/sequence/src/test/tsconfig.json index e4c50048cad9..d1d59650d306 100644 --- a/packages/dds/sequence/src/test/tsconfig.json +++ b/packages/dds/sequence/src/test/tsconfig.json @@ -14,5 +14,6 @@ "outDir": "../../dist/test", "types": ["mocha"], "noUnusedLocals": false, // Need it so memory tests can declare local variables just for the sake of keeping things in memory, + "noImplicitAny": false, }, } diff --git a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index ec03517767de..0a02a05f95fd 100644 --- a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts +++ b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts @@ -415,6 +415,7 @@ declare function get_old_InterfaceDeclaration_ISharedString(): declare function use_current_InterfaceDeclaration_ISharedString( use: TypeOnly): void; use_current_InterfaceDeclaration_ISharedString( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_ISharedString()); /* @@ -427,6 +428,7 @@ declare function get_current_InterfaceDeclaration_ISharedString(): declare function use_old_InterfaceDeclaration_ISharedString( use: TypeOnly): void; use_old_InterfaceDeclaration_ISharedString( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ISharedString()); /* @@ -943,6 +945,7 @@ declare function get_old_ClassDeclaration_SharedSegmentSequence(): declare function use_current_ClassDeclaration_SharedSegmentSequence( use: TypeOnly>): void; use_current_ClassDeclaration_SharedSegmentSequence( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSegmentSequence()); /* @@ -955,6 +958,7 @@ declare function get_current_ClassDeclaration_SharedSegmentSequence(): declare function use_old_ClassDeclaration_SharedSegmentSequence( use: TypeOnly>): void; use_old_ClassDeclaration_SharedSegmentSequence( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSegmentSequence()); /* @@ -967,6 +971,7 @@ declare function get_old_ClassDeclaration_SharedSequence(): declare function use_current_ClassDeclaration_SharedSequence( use: TypeOnly>): void; use_current_ClassDeclaration_SharedSequence( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSequence()); /* @@ -979,6 +984,7 @@ declare function get_current_ClassDeclaration_SharedSequence(): declare function use_old_ClassDeclaration_SharedSequence( use: TypeOnly>): void; use_old_ClassDeclaration_SharedSequence( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSequence()); /* @@ -991,6 +997,7 @@ declare function get_old_ClassDeclaration_SharedString(): declare function use_current_ClassDeclaration_SharedString( use: TypeOnly): void; use_current_ClassDeclaration_SharedString( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedString()); /* @@ -1003,6 +1010,7 @@ declare function get_current_ClassDeclaration_SharedString(): declare function use_old_ClassDeclaration_SharedString( use: TypeOnly): void; use_old_ClassDeclaration_SharedString( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedString()); /* @@ -1075,6 +1083,7 @@ declare function get_current_TypeAliasDeclaration_SharedStringSegment(): declare function use_old_TypeAliasDeclaration_SharedStringSegment( use: TypeOnly): void; use_old_TypeAliasDeclaration_SharedStringSegment( + // @ts-expect-error compatibility expected to be broken get_current_TypeAliasDeclaration_SharedStringSegment()); /* diff --git a/packages/dds/sequence/tsconfig.json b/packages/dds/sequence/tsconfig.json index adc0e767997c..6728ced1f801 100644 --- a/packages/dds/sequence/tsconfig.json +++ b/packages/dds/sequence/tsconfig.json @@ -8,5 +8,6 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./dist", + "noImplicitAny": true, }, } diff --git a/packages/dds/shared-object-base/package.json b/packages/dds/shared-object-base/package.json index 7cab09d26d9b..e519be0402bc 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid base class for shared distributed data structures", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/shared-object-base/src/packageVersion.ts b/packages/dds/shared-object-base/src/packageVersion.ts index 42e4dcd61832..683512a78b76 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/shared-object-base/src/remoteObjectHandle.ts b/packages/dds/shared-object-base/src/remoteObjectHandle.ts index f2a58aacb32d..be8c464c976b 100644 --- a/packages/dds/shared-object-base/src/remoteObjectHandle.ts +++ b/packages/dds/shared-object-base/src/remoteObjectHandle.ts @@ -9,16 +9,9 @@ import { IFluidHandle, IFluidHandleContext, IRequest, - IResponse, FluidObject, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, } from "@fluidframework/core-interfaces"; -import { - create404Response, - exceptionToResponse, - responseToException, -} from "@fluidframework/runtime-utils"; +import { responseToException } from "@fluidframework/runtime-utils"; /** * This handle is used to dynamically load a Fluid object on a remote client and is created on parsing a serialized @@ -28,12 +21,6 @@ import { * IFluidHandle can be retrieved by calling `get` on it. */ export class RemoteFluidObjectHandle implements IFluidHandle { - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this; - } public get IFluidHandleContext() { return this; } @@ -86,21 +73,4 @@ export class RemoteFluidObjectHandle implements IFluidHandle { public bind(handle: IFluidHandle): void { handle.attachGraph(); } - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public async request(request: IRequest): Promise { - try { - // eslint-disable-next-line import/no-deprecated - const object: FluidObject = await this.get(); - const router = object.IFluidRouter; - - return router === undefined - ? create404Response(request) - : await router.request(request); - } catch (error) { - return exceptionToResponse(error); - } - } } diff --git a/packages/dds/shared-summary-block/package.json b/packages/dds/shared-summary-block/package.json index 5a2bdff00337..7abeeb23330c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A DDS that does not generate ops but is part of summary", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/shared-summary-block/src/interfaces.ts b/packages/dds/shared-summary-block/src/interfaces.ts index 7cf95160bfc3..c7ee82c4366c 100644 --- a/packages/dds/shared-summary-block/src/interfaces.ts +++ b/packages/dds/shared-summary-block/src/interfaces.ts @@ -18,6 +18,9 @@ export interface ISharedSummaryBlock extends ISharedObject { * Retrieves the given key from the map. * @param key - Key to retrieve from. * @returns The stored value, or undefined if the key is not set. + * + * @privateRemarks + * The return type is underspecified to allow for the possibility of objects with function or undefined values. */ get(key: string): Jsonable; diff --git a/packages/dds/shared-summary-block/src/packageVersion.ts b/packages/dds/shared-summary-block/src/packageVersion.ts index c9b6b8f8ad7a..68d99af00181 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/shared-summary-block/src/sharedSummaryBlock.ts b/packages/dds/shared-summary-block/src/sharedSummaryBlock.ts index acfbe80dc4b4..fe1e6026f865 100644 --- a/packages/dds/shared-summary-block/src/sharedSummaryBlock.ts +++ b/packages/dds/shared-summary-block/src/sharedSummaryBlock.ts @@ -28,7 +28,7 @@ const snapshotFileName = "header"; * Directly used in JSON.stringify, direct result from JSON.parse. */ interface ISharedSummaryBlockDataSerializable { - [key: string]: Jsonable; + [key: string]: Jsonable; } /** @@ -60,7 +60,7 @@ export class SharedSummaryBlock extends SharedObject implements ISharedSummaryBl /** * The data held by this object. */ - private readonly data = new Map(); + private readonly data = new Map>(); /** * Constructs a new SharedSummaryBlock. If the object is non-local, an id and service interfaces will diff --git a/packages/dds/task-manager/package.json b/packages/dds/task-manager/package.json index d5660a29ba4f..d90d06555a72 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed data structure for queueing exclusive tasks", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/task-manager/src/packageVersion.ts b/packages/dds/task-manager/src/packageVersion.ts index ea525a3875d9..3ba6dff1ee01 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/test-dds-utils/package.json b/packages/dds/test-dds-utils/package.json index 90bb0d1f4514..b97aa502df8b 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid DDS test utilities", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/debugger/package.json b/packages/drivers/debugger/package.json index 07e35324db05..d290fa85061a 100644 --- a/packages/drivers/debugger/package.json +++ b/packages/drivers/debugger/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/debugger", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Debugger - a tool to play through history of a file", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/debugger/src/messageSchema.ts b/packages/drivers/debugger/src/messageSchema.ts index 04d4a0442f37..6c5c0b5a5ff0 100644 --- a/packages/drivers/debugger/src/messageSchema.ts +++ b/packages/drivers/debugger/src/messageSchema.ts @@ -362,17 +362,6 @@ const mergeTreeDeltaOpSchema = { { properties: { type: { enum: [2] }, - combiningOp: { - type: "object", - properties: { - defaultValue: {}, - maxValue: {}, - minValue: {}, - name: { type: "string" }, - }, - required: ["name"], - additionalProperties: false, - }, pos1: { type: "number" }, pos2: { type: "number" }, props: { type: "object" }, diff --git a/packages/drivers/driver-base/package.json b/packages/drivers/driver-base/package.json index a6fc06930f1c..04e16468cf7f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Shared driver code for Fluid driver implementations", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/driver-base/src/packageVersion.ts b/packages/drivers/driver-base/src/packageVersion.ts index 58bc52699ece..cded96d5b30a 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/drivers/driver-web-cache/package.json b/packages/drivers/driver-web-cache/package.json index 1603063680dd..50cb866a0cda 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Implementation of the driver caching API for a web browser", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/driver-web-cache/src/packageVersion.ts b/packages/drivers/driver-web-cache/src/packageVersion.ts index 84ea8bc5cdf4..0ac7139f9528 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/drivers/file-driver/package.json b/packages/drivers/file-driver/package.json index 3b7dfbd357a5..411b639a5f72 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A driver that reads/write from/to local file storage.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/fluidapp-odsp-urlResolver/package.json b/packages/drivers/fluidapp-odsp-urlResolver/package.json index 1abc171de2fc..30bf678ffa23 100644 --- a/packages/drivers/fluidapp-odsp-urlResolver/package.json +++ b/packages/drivers/fluidapp-odsp-urlResolver/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/fluidapp-odsp-urlresolver", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Url Resolver for Fluid app urls.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/local-driver/api-report/local-driver.api.md b/packages/drivers/local-driver/api-report/local-driver.api.md index ec24df2af049..446acb604de7 100644 --- a/packages/drivers/local-driver/api-report/local-driver.api.md +++ b/packages/drivers/local-driver/api-report/local-driver.api.md @@ -101,7 +101,7 @@ export class LocalDocumentStorageService implements IDocumentStorageService { // (undocumented) readBlob(blobId: string): Promise; // (undocumented) - get repositoryUrl(): string; + readonly repositoryUrl: string; // (undocumented) uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise; } diff --git a/packages/drivers/local-driver/package.json b/packages/drivers/local-driver/package.json index b8907b673dc9..cf64078688b1 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid local driver", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/local-driver/src/localDocumentStorageService.ts b/packages/drivers/local-driver/src/localDocumentStorageService.ts index ba2ab8e101f2..dfb092faf8e9 100644 --- a/packages/drivers/local-driver/src/localDocumentStorageService.ts +++ b/packages/drivers/local-driver/src/localDocumentStorageService.ts @@ -36,10 +36,7 @@ export class LocalDocumentStorageService implements IDocumentStorageService { protected readonly blobsShaCache = new Map(); private readonly summaryTreeUploadManager: ISummaryUploadManager; - // eslint-disable-next-line @typescript-eslint/class-literal-property-style - public get repositoryUrl(): string { - return ""; - } + public readonly repositoryUrl: string = ""; constructor( private readonly id: string, diff --git a/packages/drivers/odsp-driver-definitions/package.json b/packages/drivers/odsp-driver-definitions/package.json index 726a51769f2d..9954b4581d31 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Socket storage implementation for SPO and ODC", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/odsp-driver/package.json b/packages/drivers/odsp-driver/package.json index e27c1af27337..d86523b40a70 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Socket storage implementation for SPO and ODC", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/odsp-driver/src/packageVersion.ts b/packages/drivers/odsp-driver/src/packageVersion.ts index 1ec2fe0986b1..c5b0f97b14b6 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/drivers/odsp-urlResolver/package.json b/packages/drivers/odsp-urlResolver/package.json index 08d3d8165ab7..e03f94b460b5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Url Resolver for odsp urls.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/replay-driver/package.json b/packages/drivers/replay-driver/package.json index 562aa6d12747..7c577584085c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Document replay version of Socket.IO implementation", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/routerlicious-driver/package.json b/packages/drivers/routerlicious-driver/package.json index 7d6e223bb1b9..ffd47c1284df 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Socket.IO + Git implementation of Fluid service API", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/routerlicious-driver/src/packageVersion.ts b/packages/drivers/routerlicious-driver/src/packageVersion.ts index 192cb1f600c4..d19fd7836547 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/drivers/routerlicious-urlResolver/package.json b/packages/drivers/routerlicious-urlResolver/package.json index 59139ffb3035..0241933911bf 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Url Resolver for routerlicious urls.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/tinylicious-driver/package.json b/packages/drivers/tinylicious-driver/package.json index 2a37327f87e5..7adae8d1128e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Driver for tinylicious", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/agent-scheduler/package.json b/packages/framework/agent-scheduler/package.json index a87baa704755..971bfc0c3547 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Built in runtime object for distributing agents across instances of a container", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/aqueduct/api-report/aqueduct.api.md b/packages/framework/aqueduct/api-report/aqueduct.api.md index 76a6e084da61..2e606fcfe0e4 100644 --- a/packages/framework/aqueduct/api-report/aqueduct.api.md +++ b/packages/framework/aqueduct/api-report/aqueduct.api.md @@ -10,7 +10,6 @@ import { FluidDataStoreRuntime } from '@fluidframework/datastore'; import { FluidObject } from '@fluidframework/core-interfaces'; import { FluidObjectSymbolProvider } from '@fluidframework/synthesize'; import { IChannelFactory } from '@fluidframework/datastore-definitions'; -import { IContainer } from '@fluidframework/container-definitions'; import { IContainerContext } from '@fluidframework/container-definitions'; import { IContainerRuntime } from '@fluidframework/container-runtime-definitions'; import { IContainerRuntimeBase } from '@fluidframework/runtime-definitions'; @@ -24,8 +23,6 @@ import { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions'; import { IFluidDependencySynthesizer } from '@fluidframework/synthesize'; import { IFluidHandle } from '@fluidframework/core-interfaces'; import { IFluidLoadable } from '@fluidframework/core-interfaces'; -import type { IFluidMountableViewClass } from '@fluidframework/view-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IProvideFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IProvideFluidHandle } from '@fluidframework/core-interfaces'; import { IRequest } from '@fluidframework/core-interfaces'; @@ -33,7 +30,6 @@ import { IResponse } from '@fluidframework/core-interfaces'; import { ISharedDirectory } from '@fluidframework/map'; import { NamedFluidDataStoreRegistryEntries } from '@fluidframework/runtime-definitions'; import { NamedFluidDataStoreRegistryEntry } from '@fluidframework/runtime-definitions'; -import { RequestParser } from '@fluidframework/runtime-utils'; import { RuntimeFactoryHelper } from '@fluidframework/runtime-utils'; import { RuntimeRequestHandler } from '@fluidframework/request-handler'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; @@ -95,21 +91,6 @@ export interface DataObjectTypes { OptionalProviders?: FluidObject; } -// @internal @deprecated -export function defaultFluidObjectRequestHandler(fluidObject: FluidObject, request: IRequest): IResponse; - -// @internal @deprecated -export const defaultRouteRequestHandler: (defaultRootId: string) => (request: IRequest, runtime: IContainerRuntime) => Promise; - -// @internal @deprecated (undocumented) -export function getDefaultObjectFromContainer(container: IContainer): Promise; - -// @internal @deprecated (undocumented) -export function getObjectFromContainer(path: string, container: IContainer): Promise; - -// @internal @deprecated (undocumented) -export function getObjectWithIdFromContainer(id: string, container: IContainer): Promise; - // @alpha (undocumented) export interface IDataObjectProps { // (undocumented) @@ -122,17 +103,8 @@ export interface IDataObjectProps { readonly runtime: IFluidDataStoreRuntime; } -// @alpha @deprecated -export interface IRootDataObjectFactory extends IFluidDataStoreFactory { - // (undocumented) - createRootInstance(rootDataStoreId: string, runtime: IContainerRuntime): Promise; -} - -// @internal @deprecated -export const mountableViewRequestHandler: (MountableViewClass: IFluidMountableViewClass, handlers: RuntimeRequestHandler[]) => (request: RequestParser, runtime: IContainerRuntime) => Promise; - // @alpha -export abstract class PureDataObject extends TypedEventEmitter implements IFluidLoadable, IFluidRouter, IProvideFluidHandle { +export abstract class PureDataObject extends TypedEventEmitter implements IFluidLoadable, IProvideFluidHandle { constructor(props: IDataObjectProps); protected readonly context: IFluidDataStoreContext; finishInitialization(existing: boolean): Promise; @@ -146,8 +118,6 @@ export abstract class PureDataObject; // (undocumented) get IFluidLoadable(): this; - // @deprecated (undocumented) - get IFluidRouter(): this; initializeInternal(existing: boolean): Promise; // (undocumented) protected initializeP: Promise | undefined; @@ -162,7 +132,7 @@ export abstract class PureDataObject, I extends DataObjectTypes = DataObjectTypes> implements IFluidDataStoreFactory, Partial, IRootDataObjectFactory { +export class PureDataObjectFactory, I extends DataObjectTypes = DataObjectTypes> implements IFluidDataStoreFactory, Partial { constructor(type: string, ctor: new (props: IDataObjectProps) => TObj, sharedObjects: readonly IChannelFactory[], optionalProviders: FluidObjectSymbolProvider, registryEntries?: NamedFluidDataStoreRegistryEntries, runtimeClass?: typeof FluidDataStoreRuntime); createChildInstance(parentContext: IFluidDataStoreContext, initialState?: I["InitialState"]): Promise; createInstance(runtime: IContainerRuntimeBase, initialState?: I["InitialState"]): Promise; diff --git a/packages/framework/aqueduct/package.json b/packages/framework/aqueduct/package.json index 33b936acaa7d..aaa3b168fdf3 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/aqueduct", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A set of implementations for Fluid Framework interfaces.", "homepage": "https://fluidframework.com", "repository": { @@ -123,6 +123,44 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedFunctionDeclaration_getDefaultObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_getObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_getObjectWithIdFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_defaultFluidObjectRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_defaultRouteRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_mountableViewRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_DataObject": { + "backCompat": false + }, + "RemovedInterfaceDeclaration_IRootDataObjectFactory": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_PureDataObject": { + "backCompat": false + }, + "InterfaceDeclaration_IDataObjectProps": { + "backCompat": false + } + } } } diff --git a/packages/framework/aqueduct/src/container-runtime-factories/baseContainerRuntimeFactory.ts b/packages/framework/aqueduct/src/container-runtime-factories/baseContainerRuntimeFactory.ts index 0722145e376d..81700d25a161 100644 --- a/packages/framework/aqueduct/src/container-runtime-factories/baseContainerRuntimeFactory.ts +++ b/packages/framework/aqueduct/src/container-runtime-factories/baseContainerRuntimeFactory.ts @@ -57,7 +57,7 @@ export class BaseContainerRuntimeFactory constructor(props: { registryEntries: NamedFluidDataStoreRegistryEntries; dependencyContainer?: IFluidDependencySynthesizer; - /** @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ + /** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ requestHandlers?: RuntimeRequestHandler[]; runtimeOptions?: IContainerRuntimeOptions; provideEntryPoint: (runtime: IContainerRuntime) => Promise; diff --git a/packages/framework/aqueduct/src/container-runtime-factories/containerRuntimeFactoryWithDefaultDataStore.ts b/packages/framework/aqueduct/src/container-runtime-factories/containerRuntimeFactoryWithDefaultDataStore.ts index 58c3c9bdc227..4c8ba75031e7 100644 --- a/packages/framework/aqueduct/src/container-runtime-factories/containerRuntimeFactoryWithDefaultDataStore.ts +++ b/packages/framework/aqueduct/src/container-runtime-factories/containerRuntimeFactoryWithDefaultDataStore.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { IContainerRuntimeOptions } from "@fluidframework/container-runtime"; +import { ContainerRuntime, IContainerRuntimeOptions } from "@fluidframework/container-runtime"; import { IFluidDataStoreFactory, NamedFluidDataStoreRegistryEntries, @@ -11,9 +11,8 @@ import { import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; import { IFluidDependencySynthesizer } from "@fluidframework/synthesize"; import { RuntimeRequestHandler } from "@fluidframework/request-handler"; -import { FluidObject } from "@fluidframework/core-interfaces"; -// eslint-disable-next-line import/no-deprecated -import { defaultRouteRequestHandler } from "../request-handlers"; +import { FluidObject, IRequest } from "@fluidframework/core-interfaces"; +import { RequestParser } from "@fluidframework/runtime-utils"; import { BaseContainerRuntimeFactory } from "./baseContainerRuntimeFactory"; const defaultDataStoreId = "default"; @@ -51,7 +50,7 @@ export class ContainerRuntimeFactoryWithDefaultDataStore extends BaseContainerRu defaultFactory: IFluidDataStoreFactory; registryEntries: NamedFluidDataStoreRegistryEntries; dependencyContainer?: IFluidDependencySynthesizer; - /** @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ + /** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ requestHandlers?: RuntimeRequestHandler[]; runtimeOptions?: IContainerRuntimeOptions; provideEntryPoint?: (runtime: IContainerRuntime) => Promise; @@ -59,10 +58,21 @@ export class ContainerRuntimeFactoryWithDefaultDataStore extends BaseContainerRu const requestHandlers = props.requestHandlers ?? []; const provideEntryPoint = props.provideEntryPoint ?? getDefaultFluidObject; + const getDefaultObject = async (request: IRequest, runtime: IContainerRuntime) => { + const parser = RequestParser.create(request); + if (parser.pathParts.length === 0) { + // This cast is safe as ContainerRuntime.loadRuntime is called in the base class + return (runtime as ContainerRuntime).resolveHandle({ + url: `/${defaultDataStoreId}${parser.query}`, + headers: request.headers, + }); + } + return undefined; // continue search + }; + super({ ...props, - // eslint-disable-next-line import/no-deprecated - requestHandlers: [defaultRouteRequestHandler(defaultDataStoreId), ...requestHandlers], + requestHandlers: [getDefaultObject, ...requestHandlers], provideEntryPoint, }); diff --git a/packages/framework/aqueduct/src/data-object-factories/index.ts b/packages/framework/aqueduct/src/data-object-factories/index.ts index e21da27c1d3f..d7e0aca650a7 100644 --- a/packages/framework/aqueduct/src/data-object-factories/index.ts +++ b/packages/framework/aqueduct/src/data-object-factories/index.ts @@ -4,4 +4,4 @@ */ export { DataObjectFactory } from "./dataObjectFactory"; -export { IRootDataObjectFactory, PureDataObjectFactory } from "./pureDataObjectFactory"; +export { PureDataObjectFactory } from "./pureDataObjectFactory"; diff --git a/packages/framework/aqueduct/src/data-object-factories/pureDataObjectFactory.ts b/packages/framework/aqueduct/src/data-object-factories/pureDataObjectFactory.ts index be01ce212753..d8882fac62cf 100644 --- a/packages/framework/aqueduct/src/data-object-factories/pureDataObjectFactory.ts +++ b/packages/framework/aqueduct/src/data-object-factories/pureDataObjectFactory.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-deprecated -import { IRequest, IFluidRouter, FluidObject } from "@fluidframework/core-interfaces"; +import { IRequest, FluidObject } from "@fluidframework/core-interfaces"; import { FluidDataStoreRuntime, ISharedObjectRegistry, @@ -31,15 +30,6 @@ import { import { assert } from "@fluidframework/core-utils"; import { IDataObjectProps, PureDataObject, DataObjectTypes } from "../data-objects"; -/** - * Useful interface in places where it's useful to do type erasure for PureDataObject generic - * @deprecated Will be removed in future major release. Please remove all usage of it. - * @alpha - */ -export interface IRootDataObjectFactory extends IFluidDataStoreFactory { - // eslint-disable-next-line import/no-deprecated - createRootInstance(rootDataStoreId: string, runtime: IContainerRuntime): Promise; -} /** * Proxy over PureDataObject @@ -136,10 +126,7 @@ export class PureDataObjectFactory< TObj extends PureDataObject, I extends DataObjectTypes = DataObjectTypes, > - implements - IFluidDataStoreFactory, - Partial, - IRootDataObjectFactory + implements IFluidDataStoreFactory, Partial { private readonly sharedObjectRegistry: ISharedObjectRegistry; private readonly registry: IFluidDataStoreRegistry | undefined; diff --git a/packages/framework/aqueduct/src/data-objects/pureDataObject.ts b/packages/framework/aqueduct/src/data-objects/pureDataObject.ts index b4f9e63e42cd..3a3f5caff08b 100644 --- a/packages/framework/aqueduct/src/data-objects/pureDataObject.ts +++ b/packages/framework/aqueduct/src/data-objects/pureDataObject.ts @@ -9,8 +9,6 @@ import { IEvent, IFluidHandle, IFluidLoadable, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IProvideFluidHandle, IRequest, IResponse, @@ -31,8 +29,7 @@ import { DataObjectTypes, IDataObjectProps } from "./types"; */ export abstract class PureDataObject extends TypedEventEmitter - // eslint-disable-next-line import/no-deprecated - implements IFluidLoadable, IFluidRouter, IProvideFluidHandle + implements IFluidLoadable, IProvideFluidHandle { /** * This is your FluidDataStoreRuntime object @@ -60,13 +57,6 @@ export abstract class PureDataObject { - // eslint-disable-next-line import/no-deprecated - const nestedHandler = buildRuntimeRequestHandler(...handlers); - return async (request: RequestParser, runtime: IContainerRuntime) => { - const mountableView = request.headers?.mountableView === true; - let newRequest: IRequest = request; - if (mountableView) { - // Reissue the request without the mountableView header. - // We'll repack whatever the response is if we can. - const headers: IRequestHeader = { ...request.headers }; - delete (headers as any).mountableView; - newRequest = { - url: request.url, - headers, - }; - } - const response = await nestedHandler(newRequest, runtime); - - if ( - mountableView && - response.status === 200 && - MountableViewClass.canMount(response.value) - ) { - return { - status: 200, - mimeType: "fluid/object", - value: new MountableViewClass(response.value), - }; - } - return response; - }; -}; - -/** - * Pipe through container request into internal request. - * If request is empty and default url is provided, redirect request to such default url. - * @param defaultRootId - optional default root data store ID to pass request in case request is empty. - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export const defaultRouteRequestHandler = (defaultRootId: string) => { - return async (request: IRequest, runtime: IContainerRuntime) => { - const parser = RequestParser.create(request); - if (parser.pathParts.length === 0) { - return runtime.IFluidHandleContext.resolveHandle({ - url: `/${defaultRootId}${parser.query}`, - headers: request.headers, - }); - } - return undefined; // continue search - }; -}; - -/** - * Default request handler for a Fluid object that returns the object itself if: - * - * 1. the request url is empty - * - * 2. the request url is "/" - * - * 3. the request url starts with "/" and is followed by a query param, such as /?key=value - * - * Returns a 404 error for any other url. - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export function defaultFluidObjectRequestHandler( - fluidObject: FluidObject, - request: IRequest, -): IResponse { - return request.url === "" || request.url === "/" || request.url.startsWith("/?") - ? { mimeType: "fluid/object", status: 200, value: fluidObject } - : create404Response(request); -} diff --git a/examples/data-objects/shared-text/src/client-ui-lib/ui/debug.ts b/packages/framework/aqueduct/src/test/aqueduct.spec.ts similarity index 52% rename from examples/data-objects/shared-text/src/client-ui-lib/ui/debug.ts rename to packages/framework/aqueduct/src/test/aqueduct.spec.ts index a26f2d51305c..72d47def029f 100644 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/debug.ts +++ b/packages/framework/aqueduct/src/test/aqueduct.spec.ts @@ -3,6 +3,5 @@ * Licensed under the MIT License. */ -import registerDebug from "debug"; - -export const debug = registerDebug("fluid:ui"); +// Build pipeline breaks without this file ("No test files found") +describe("aqueduct-placeholder", () => {}); diff --git a/packages/framework/aqueduct/src/test/defaultRoute.spec.ts b/packages/framework/aqueduct/src/test/defaultRoute.spec.ts deleted file mode 100644 index 2691d11c4ea4..000000000000 --- a/packages/framework/aqueduct/src/test/defaultRoute.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ - -import { strict as assert } from "assert"; -import { RequestParser, create404Response } from "@fluidframework/runtime-utils"; -import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IFluidDataStoreChannel } from "@fluidframework/runtime-definitions"; -import { IRequest, IResponse, IFluidRouter } from "@fluidframework/core-interfaces"; -import { createFluidObjectResponse } from "@fluidframework/request-handler"; -import { defaultRouteRequestHandler } from "../request-handlers"; - -class MockRuntime { - public get IFluidHandleContext() { - return this; - } - - public async getRootDataStore(id, wait): Promise { - if (id === "objectId") { - return { - request: async (r) => { - if (r.url === "/" || r.url === "/route") { - return createFluidObjectResponse({ route: r.url }); - } - return create404Response(r); - }, - } as IFluidDataStoreChannel; - } - - assert(wait !== true); - throw new Error("No data store"); - } - - protected async resolveHandle(request: IRequest) { - const requestParser = RequestParser.create(request); - - if (requestParser.pathParts.length > 0) { - const wait = - typeof request.headers?.wait === "boolean" ? request.headers.wait : undefined; - - const dataStore = await this.getRootDataStore(requestParser.pathParts[0], wait); - const subRequest = requestParser.createSubRequest(1); - return dataStore.request(subRequest); - } - return create404Response(request); - } -} - -async function assertRejected(p: Promise) { - try { - const res = await p; - assert(res === undefined || res.status === 404, "not rejected"); - } catch (err) {} -} - -// TODO: Remove this file when defaultRouteRequestHandler is removed AB#4991 -describe("defaultRouteRequestHandler", () => { - const runtime = new MockRuntime() as any as IContainerRuntime; - - it("Data store request with default ID", async () => { - const handler = defaultRouteRequestHandler("objectId"); - - const requestParser = RequestParser.create({ url: "", headers: {} }); - const response = await handler(requestParser, runtime); - assert(response); - assert.equal(response.status, 200); - assert.equal(response.value.route, "/"); - - const requestParser2 = RequestParser.create({ url: "/", headers: {} }); - const response2 = await handler(requestParser2, runtime); - assert(response2); - assert.equal(response2.status, 200); - assert.equal(response.value.route, "/"); - }); - - it("Data store request with non-existing default ID", async () => { - const handler = defaultRouteRequestHandler("foobar"); - - const requestParser = RequestParser.create({ url: "", headers: { wait: true } }); - const responseP = handler(requestParser, runtime); - await assertRejected(responseP); - - const requestParser2 = RequestParser.create({ url: "/", headers: { wait: true } }); - const responseP2 = handler(requestParser2, runtime); - await assertRejected(responseP2); - }); -}); diff --git a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts index 0134ddde9939..fcf361b4ca12 100644 --- a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts +++ b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts @@ -91,6 +91,7 @@ declare function get_current_ClassDeclaration_DataObject(): declare function use_old_ClassDeclaration_DataObject( use: TypeOnly): void; use_old_ClassDeclaration_DataObject( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_DataObject()); /* @@ -163,31 +164,20 @@ declare function get_current_InterfaceDeclaration_IDataObjectProps(): declare function use_old_InterfaceDeclaration_IDataObjectProps( use: TypeOnly): void; use_old_InterfaceDeclaration_IDataObjectProps( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IDataObjectProps()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IRootDataObjectFactory": {"forwardCompat": false} +* "RemovedInterfaceDeclaration_IRootDataObjectFactory": {"forwardCompat": false} */ -declare function get_old_InterfaceDeclaration_IRootDataObjectFactory(): - TypeOnly; -declare function use_current_InterfaceDeclaration_IRootDataObjectFactory( - use: TypeOnly): void; -use_current_InterfaceDeclaration_IRootDataObjectFactory( - get_old_InterfaceDeclaration_IRootDataObjectFactory()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "InterfaceDeclaration_IRootDataObjectFactory": {"backCompat": false} +* "RemovedInterfaceDeclaration_IRootDataObjectFactory": {"backCompat": false} */ -declare function get_current_InterfaceDeclaration_IRootDataObjectFactory(): - TypeOnly; -declare function use_old_InterfaceDeclaration_IRootDataObjectFactory( - use: TypeOnly): void; -use_old_InterfaceDeclaration_IRootDataObjectFactory( - get_current_InterfaceDeclaration_IRootDataObjectFactory()); /* * Validate forward compat by using old type in place of current type @@ -211,6 +201,7 @@ declare function get_current_ClassDeclaration_PureDataObject(): declare function use_old_ClassDeclaration_PureDataObject( use: TypeOnly): void; use_old_ClassDeclaration_PureDataObject( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_PureDataObject()); /* @@ -240,143 +231,71 @@ use_old_ClassDeclaration_PureDataObjectFactory( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_defaultFluidObjectRequestHandler": {"forwardCompat": false} +* "RemovedFunctionDeclaration_defaultFluidObjectRequestHandler": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_defaultFluidObjectRequestHandler(): - TypeOnly; -declare function use_current_FunctionDeclaration_defaultFluidObjectRequestHandler( - use: TypeOnly): void; -use_current_FunctionDeclaration_defaultFluidObjectRequestHandler( - get_old_FunctionDeclaration_defaultFluidObjectRequestHandler()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_defaultFluidObjectRequestHandler": {"backCompat": false} +* "RemovedFunctionDeclaration_defaultFluidObjectRequestHandler": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_defaultFluidObjectRequestHandler(): - TypeOnly; -declare function use_old_FunctionDeclaration_defaultFluidObjectRequestHandler( - use: TypeOnly): void; -use_old_FunctionDeclaration_defaultFluidObjectRequestHandler( - get_current_FunctionDeclaration_defaultFluidObjectRequestHandler()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_defaultRouteRequestHandler": {"forwardCompat": false} +* "RemovedVariableDeclaration_defaultRouteRequestHandler": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_defaultRouteRequestHandler(): - TypeOnly; -declare function use_current_VariableDeclaration_defaultRouteRequestHandler( - use: TypeOnly): void; -use_current_VariableDeclaration_defaultRouteRequestHandler( - get_old_VariableDeclaration_defaultRouteRequestHandler()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_defaultRouteRequestHandler": {"backCompat": false} +* "RemovedVariableDeclaration_defaultRouteRequestHandler": {"backCompat": false} */ -declare function get_current_VariableDeclaration_defaultRouteRequestHandler(): - TypeOnly; -declare function use_old_VariableDeclaration_defaultRouteRequestHandler( - use: TypeOnly): void; -use_old_VariableDeclaration_defaultRouteRequestHandler( - get_current_VariableDeclaration_defaultRouteRequestHandler()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getDefaultObjectFromContainer": {"forwardCompat": false} +* "RemovedFunctionDeclaration_getDefaultObjectFromContainer": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_getDefaultObjectFromContainer(): - TypeOnly; -declare function use_current_FunctionDeclaration_getDefaultObjectFromContainer( - use: TypeOnly): void; -use_current_FunctionDeclaration_getDefaultObjectFromContainer( - get_old_FunctionDeclaration_getDefaultObjectFromContainer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getDefaultObjectFromContainer": {"backCompat": false} +* "RemovedFunctionDeclaration_getDefaultObjectFromContainer": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_getDefaultObjectFromContainer(): - TypeOnly; -declare function use_old_FunctionDeclaration_getDefaultObjectFromContainer( - use: TypeOnly): void; -use_old_FunctionDeclaration_getDefaultObjectFromContainer( - get_current_FunctionDeclaration_getDefaultObjectFromContainer()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getObjectFromContainer": {"forwardCompat": false} +* "RemovedFunctionDeclaration_getObjectFromContainer": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_getObjectFromContainer(): - TypeOnly; -declare function use_current_FunctionDeclaration_getObjectFromContainer( - use: TypeOnly): void; -use_current_FunctionDeclaration_getObjectFromContainer( - get_old_FunctionDeclaration_getObjectFromContainer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getObjectFromContainer": {"backCompat": false} +* "RemovedFunctionDeclaration_getObjectFromContainer": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_getObjectFromContainer(): - TypeOnly; -declare function use_old_FunctionDeclaration_getObjectFromContainer( - use: TypeOnly): void; -use_old_FunctionDeclaration_getObjectFromContainer( - get_current_FunctionDeclaration_getObjectFromContainer()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getObjectWithIdFromContainer": {"forwardCompat": false} +* "RemovedFunctionDeclaration_getObjectWithIdFromContainer": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_getObjectWithIdFromContainer(): - TypeOnly; -declare function use_current_FunctionDeclaration_getObjectWithIdFromContainer( - use: TypeOnly): void; -use_current_FunctionDeclaration_getObjectWithIdFromContainer( - get_old_FunctionDeclaration_getObjectWithIdFromContainer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_getObjectWithIdFromContainer": {"backCompat": false} +* "RemovedFunctionDeclaration_getObjectWithIdFromContainer": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_getObjectWithIdFromContainer(): - TypeOnly; -declare function use_old_FunctionDeclaration_getObjectWithIdFromContainer( - use: TypeOnly): void; -use_old_FunctionDeclaration_getObjectWithIdFromContainer( - get_current_FunctionDeclaration_getObjectWithIdFromContainer()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_mountableViewRequestHandler": {"forwardCompat": false} +* "RemovedVariableDeclaration_mountableViewRequestHandler": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_mountableViewRequestHandler(): - TypeOnly; -declare function use_current_VariableDeclaration_mountableViewRequestHandler( - use: TypeOnly): void; -use_current_VariableDeclaration_mountableViewRequestHandler( - get_old_VariableDeclaration_mountableViewRequestHandler()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_mountableViewRequestHandler": {"backCompat": false} +* "RemovedVariableDeclaration_mountableViewRequestHandler": {"backCompat": false} */ -declare function get_current_VariableDeclaration_mountableViewRequestHandler(): - TypeOnly; -declare function use_old_VariableDeclaration_mountableViewRequestHandler( - use: TypeOnly): void; -use_old_VariableDeclaration_mountableViewRequestHandler( - get_current_VariableDeclaration_mountableViewRequestHandler()); diff --git a/packages/framework/aqueduct/src/utils/containerInteractions.ts b/packages/framework/aqueduct/src/utils/containerInteractions.ts deleted file mode 100644 index 4ff4bf415487..000000000000 --- a/packages/framework/aqueduct/src/utils/containerInteractions.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ -import { FluidObject } from "@fluidframework/core-interfaces"; -import { IContainer } from "@fluidframework/container-definitions"; - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * Helper function for getting the default Fluid Object from a Container. This function only works for - * Containers that support "/" request. - * - * @typeParam T - Defines the type you expect to be returned. - * - * @param container - Container you're attempting to get the object from - * @internal - */ -export async function getDefaultObjectFromContainer( - container: IContainer, -): Promise { - const url = "/"; - const response = await container.request({ url }); - - // Verify the response - if (response.status !== 200 || response.mimeType !== "fluid/object") { - throw new Error(`Unable to retrieve Fluid object at URL: "${url}"`); - } else if (response.value === undefined) { - throw new Error(`Empty response from URL: "${url}"`); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.value; -} - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * Helper function for getting as Fluid Object from a Container given a Unique Id. This function only works for - * Containers that support getting FluidObjects via request. - * - * @typeParam T - Defines the type you expect to be returned. - * - * @param id - Unique id of the FluidObject - * @param container - Container you're attempting to get the object from - * @internal - */ -export async function getObjectWithIdFromContainer( - id: string, - container: IContainer, -): Promise { - const url = `/${id}`; - const response = await container.request({ url }); - - // Verify the response - if (response.status !== 200 || response.mimeType !== "fluid/object") { - throw new Error(`Unable to retrieve Fluid object with ID: "${id}" from URL: "${url}"`); - } else if (response.value === undefined) { - throw new Error(`Empty response for ID: "${id}" from URL: "${url}"`); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.value; -} - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * Helper function for getting a Fluid Object from a Container given a path/url. This function only works for - * Containers that support getting FluidObjects via request. - * - * @typeParam T - Defines the type you expect to be returned. - * - * @param path - Unique path/url of the FluidObject - * @param container - Container you're attempting to get the object from - * @internal - */ -export async function getObjectFromContainer( - path: string, - container: IContainer, -): Promise { - const response = await container.request({ url: path }); - - if (response.status !== 200 || response.mimeType !== "fluid/object") { - throw new Error(`Unable to retrieve Fluid object with from URL: "${path}"`); - } else if (response.value === undefined) { - throw new Error(`Empty response for from URL: "${path}"`); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.value; -} diff --git a/packages/framework/aqueduct/src/utils/index.ts b/packages/framework/aqueduct/src/utils/index.ts deleted file mode 100644 index 53c4036adb45..000000000000 --- a/packages/framework/aqueduct/src/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export { - getDefaultObjectFromContainer, - getObjectFromContainer, - getObjectWithIdFromContainer, -} from "./containerInteractions"; diff --git a/packages/framework/attributor/package.json b/packages/framework/attributor/package.json index afe48a3698d2..bc736169fe63 100644 --- a/packages/framework/attributor/package.json +++ b/packages/framework/attributor/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/attributor", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Operation attributor", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/attributor/src/encoders.ts b/packages/framework/attributor/src/encoders.ts index 51cfb9bfa6f2..8a4314b7417a 100644 --- a/packages/framework/attributor/src/encoders.ts +++ b/packages/framework/attributor/src/encoders.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ import { assert } from "@fluidframework/core-utils"; -import { Jsonable } from "@fluidframework/datastore-definitions"; import { IUser } from "@fluidframework/protocol-definitions"; import { AttributionInfo } from "@fluidframework/runtime-definitions"; import { IAttributor } from "./attributor"; @@ -29,10 +28,10 @@ export const deltaEncoder: TimestampEncoder = { } return deltaTimestamps; }, - decode: (encoded: Jsonable) => { + decode: (encoded: unknown) => { assert( Array.isArray(encoded), - 0x4b0 /* Encoded timestamps should be an array of nummbers */, + 0x4b0 /* Encoded timestamps should be an array of numbers */, ); const timestamps: number[] = new Array(encoded.length); let cumulativeSum = 0; diff --git a/packages/framework/attributor/src/mixinAttributor.ts b/packages/framework/attributor/src/mixinAttributor.ts index f61e28bd82f8..095dc2275128 100644 --- a/packages/framework/attributor/src/mixinAttributor.ts +++ b/packages/framework/attributor/src/mixinAttributor.ts @@ -100,33 +100,6 @@ export function createRuntimeAttributor(): IRuntimeAttributor { */ export const mixinAttributor = (Base: typeof ContainerRuntime = ContainerRuntime) => class ContainerRuntimeWithAttributor extends Base { - public static async load( - context: IContainerContext, - registryEntries: NamedFluidDataStoreRegistryEntries, - requestHandler?: - | ((request: IRequest, runtime: IContainerRuntime) => Promise) - | undefined, - runtimeOptions: IContainerRuntimeOptions | undefined = {}, - containerScope: FluidObject | undefined = context.scope, - existing?: boolean | undefined, - containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntimeWithAttributor as unknown as typeof ContainerRuntime, - ): Promise { - return this.loadRuntime({ - context, - registryEntries, - existing: existing ?? false, - requestHandler, - runtimeOptions, - containerScope, - containerRuntimeCtor, - provideEntryPoint: async () => { - throw new UsageError( - "ContainerRuntime.load is deprecated and should no longer be used", - ); - }, - }); - } - public static async loadRuntime(params: { context: IContainerContext; registryEntries: NamedFluidDataStoreRegistryEntries; @@ -134,7 +107,7 @@ export const mixinAttributor = (Base: typeof ContainerRuntime = ContainerRuntime runtimeOptions?: IContainerRuntimeOptions; containerScope?: FluidObject; containerRuntimeCtor?: typeof ContainerRuntime; - /** @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ + /** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise; provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise; }): Promise { diff --git a/packages/framework/attributor/src/test/attribution/sharedString.attribution.spec.ts b/packages/framework/attributor/src/test/attribution/sharedString.attribution.spec.ts index 73311bf07749..d2211b917e61 100644 --- a/packages/framework/attributor/src/test/attribution/sharedString.attribution.spec.ts +++ b/packages/framework/attributor/src/test/attribution/sharedString.attribution.spec.ts @@ -32,7 +32,7 @@ import { IFluidDataStoreRuntime, Jsonable, } from "@fluidframework/datastore-definitions"; -import { IClient, ISummaryTree } from "@fluidframework/protocol-definitions"; +import { IClient, ISummaryTree, SummaryType } from "@fluidframework/protocol-definitions"; import { IAudience } from "@fluidframework/container-definitions"; import { SharedString, SharedStringFactory } from "@fluidframework/sequence"; import { createInsertOnlyAttributionPolicy } from "@fluidframework/merge-tree"; @@ -362,11 +362,82 @@ function spyOnOperations(baseGenerator: Generator): { return { operations, generator }; } -function readJson(filepath: string): Jsonable { - return JSON.parse(readFileSync(filepath, { encoding: "utf-8" })); +/** + * Type constraint for types that are likely deserializable from JSON or have a custom + * alternate type. + */ +type JsonDeserializedTypeWith = + | null + | boolean + | number + | string + | T + | { [P in string]: JsonDeserializedTypeWith } + | JsonDeserializedTypeWith[]; + +type NonSymbolWithDefinedNonFunctionPropertyOf = Exclude< + { + // eslint-disable-next-line @typescript-eslint/ban-types + [K in keyof T]: undefined extends T[K] ? never : T[K] extends Function ? never : K; + }[keyof T], + undefined | symbol +>; +type NonSymbolWithUndefinedNonFunctionPropertyOf = Exclude< + { + // eslint-disable-next-line @typescript-eslint/ban-types + [K in keyof T]: undefined extends T[K] ? (T[K] extends Function ? never : K) : never; + }[keyof T], + undefined | symbol +>; + +/** + * Used to constrain a type `T` to types that are deserializable from JSON. + * + * When used as a filter to inferred generic `T`, a compile-time error can be + * produced trying to assign `JsonDeserialized` to `T`. + * + * Deserialized JSON never contains `undefined` values, so properties with + * `undefined` values become optional. If the original property was not already + * optional, then compilation of assignment will fail. + * + * Similarly, function valued properties are removed. + */ +type JsonDeserialized = /* test for 'any' */ boolean extends ( + T extends never ? true : false +) + ? /* 'any' => */ JsonDeserializedTypeWith + : /* test for 'unknown' */ unknown extends T + ? /* 'unknown' => */ JsonDeserializedTypeWith + : /* test for Jsonable primitive types */ T extends null | boolean | number | string | TReplaced + ? /* primitive types => */ T + : // eslint-disable-next-line @typescript-eslint/ban-types + /* test for not a function */ Extract extends never + ? /* not a function => test for object */ T extends object + ? /* object => test for array */ T extends (infer E)[] + ? /* array => */ JsonDeserialized[] + : /* property bag => */ + /* properties with symbol keys or function values are removed */ + { + /* properties with defined values are recursed */ + [K in NonSymbolWithDefinedNonFunctionPropertyOf]: JsonDeserialized< + T[K], + TReplaced + >; + } & { + /* properties that may have undefined values are optional */ + [K in NonSymbolWithUndefinedNonFunctionPropertyOf]?: JsonDeserialized< + T[K], + TReplaced + >; + } + : /* not an object => */ never + : /* function => */ never; + +function readJson(filepath: string): JsonDeserialized { + return JSON.parse(readFileSync(filepath, { encoding: "utf-8" })) as JsonDeserialized; } -function writeJson(filepath: string, content: Jsonable) { +function writeJson(filepath: string, content: Jsonable) { writeFileSync(filepath, JSON.stringify(content, undefined, 4), { encoding: "utf-8" }); } @@ -403,7 +474,37 @@ function embedAttributionInProps(operations: Operation[]): Operation[] { }); } -const summaryFromState = async (state: FuzzTestState): Promise => { +// ISummaryTree is not serializable due to Uint8Array content in ISummaryBlob. +// SerializableISummaryTree is a version of ISummaryTree with Uint8Array content removed. +type SerializableISummaryTree = ExcludeDeeply; + +type ExcludeDeeply> = TBase extends object + ? { [K in keyof TBase]: ExcludeDeeply } + : TBase; + +/** + * Validates that summary tree does not have a blob with a Uint8Array as content. + * + * @param summary - Summary tree to validate + */ +function assertSerializableSummary( + summary: ISummaryTree, +): asserts summary is SerializableISummaryTree { + Object.values(summary.tree).forEach((value) => { + switch (value.type) { + case SummaryType.Tree: + assertSerializableSummary(value); + break; + case SummaryType.Blob: + assert(typeof value.content === "string"); + break; + default: + break; + } + }); +} + +const summaryFromState = async (state: FuzzTestState): Promise => { state.containerRuntimeFactory.processAllMessages(); const { sharedString } = state.clients[0]; const { summary } = await sharedString.summarize(); @@ -412,6 +513,7 @@ const summaryFromState = async (state: FuzzTestState): Promise => if (state.attributor && state.serializer) { (summary as any).attribution = state.serializer.encode(state.attributor); } + assertSerializableSummary(summary); return summary; }; @@ -578,7 +680,7 @@ describe("SharedString Attribution", () => { let operations: Operation[]; before(() => { paths = getDocumentPaths(document); - operations = readJson(paths.operations); + operations = readJson(paths.operations); }); for (const { filename, factory } of dataGenerators) { diff --git a/packages/framework/client-logger/app-insights-logger/package.json b/packages/framework/client-logger/app-insights-logger/package.json index 383aa4c53a73..1e017e793a43 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": "@fluid-experimental/app-insights-logger", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Contains a Fluid logging client that sends telemetry events to Azure App Insights", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/data-object-base/api-report/data-object-base.api.md b/packages/framework/data-object-base/api-report/data-object-base.api.md index 5a1136c521e3..05bf1a4b7c1c 100644 --- a/packages/framework/data-object-base/api-report/data-object-base.api.md +++ b/packages/framework/data-object-base/api-report/data-object-base.api.md @@ -18,7 +18,6 @@ import { IFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IFluidDataStoreRuntime } from '@fluidframework/datastore-definitions'; import { IFluidHandle } from '@fluidframework/core-interfaces'; import { IFluidLoadable } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IProvideFluidHandle } from '@fluidframework/core-interfaces'; import { IRequest } from '@fluidframework/core-interfaces'; import { IResponse } from '@fluidframework/core-interfaces'; @@ -28,7 +27,7 @@ import { RuntimeFactoryHelper } from '@fluidframework/runtime-utils'; import { RuntimeRequestHandler } from '@fluidframework/request-handler'; // @internal (undocumented) -export abstract class LazyLoadedDataObject extends EventForwarder implements IFluidLoadable, IProvideFluidHandle, IFluidRouter { +export abstract class LazyLoadedDataObject extends EventForwarder implements IFluidLoadable, IProvideFluidHandle { constructor(context: IFluidDataStoreContext, runtime: IFluidDataStoreRuntime, root: ISharedObject); // (undocumented) protected readonly context: IFluidDataStoreContext; @@ -40,8 +39,6 @@ export abstract class LazyLoadedDataObject; // (undocumented) get IFluidLoadable(): this; - // @deprecated (undocumented) - get IFluidRouter(): this; // (undocumented) get IProvideFluidHandle(): this; // (undocumented) diff --git a/packages/framework/data-object-base/package.json b/packages/framework/data-object-base/package.json index 901fc12f2899..a3a9b0bba255 100644 --- a/packages/framework/data-object-base/package.json +++ b/packages/framework/data-object-base/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/data-object-base", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Data object base for synchronously and lazily loaded object scenarios", "homepage": "https://fluidframework.com", "repository": { @@ -87,6 +87,10 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_LazyLoadedDataObject": { + "backCompat": false + } + } } } diff --git a/packages/framework/data-object-base/src/lazyLoadedDataObject.ts b/packages/framework/data-object-base/src/lazyLoadedDataObject.ts index 154e77e6b4a6..8ce168ac74ba 100644 --- a/packages/framework/data-object-base/src/lazyLoadedDataObject.ts +++ b/packages/framework/data-object-base/src/lazyLoadedDataObject.ts @@ -10,8 +10,6 @@ import { IRequest, IResponse, IProvideFluidHandle, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, } from "@fluidframework/core-interfaces"; import { IFluidDataStoreContext } from "@fluidframework/runtime-definitions"; import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; @@ -28,18 +26,10 @@ export abstract class LazyLoadedDataObject< TEvents extends IEvent = IEvent, > extends EventForwarder - // eslint-disable-next-line import/no-deprecated - implements IFluidLoadable, IProvideFluidHandle, IFluidRouter + implements IFluidLoadable, IProvideFluidHandle { private _handle?: IFluidHandle; - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - public get IFluidRouter() { - return this; - } public get IFluidLoadable() { return this; } @@ -61,16 +51,12 @@ export abstract class LazyLoadedDataObject< this.root = root as TRoot; } - // #region IFluidRouter - public async request(r: IRequest): Promise { return r.url === "" || r.url === "/" ? { status: 200, mimeType: "fluid/object", value: this } : create404Response(r); } - // #endregion IFluidRouter - // #region IFluidLoadable public get handle(): IFluidHandle { diff --git a/packages/framework/data-object-base/src/runtimeFactory.ts b/packages/framework/data-object-base/src/runtimeFactory.ts index c5b25f3252ee..384973c88548 100644 --- a/packages/framework/data-object-base/src/runtimeFactory.ts +++ b/packages/framework/data-object-base/src/runtimeFactory.ts @@ -30,7 +30,7 @@ export class RuntimeFactory extends RuntimeFactoryHelper { constructor(props: { defaultStoreFactory: IFluidDataStoreFactory; storeFactories: IFluidDataStoreFactory[]; - /** @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ + /** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ requestHandlers?: RuntimeRequestHandler[]; provideEntryPoint: (runtime: IContainerRuntime) => Promise; }) { diff --git a/packages/framework/data-object-base/src/test/types/validateDataObjectBasePrevious.generated.ts b/packages/framework/data-object-base/src/test/types/validateDataObjectBasePrevious.generated.ts index 5033e7c568d5..afbf333b2e4b 100644 --- a/packages/framework/data-object-base/src/test/types/validateDataObjectBasePrevious.generated.ts +++ b/packages/framework/data-object-base/src/test/types/validateDataObjectBasePrevious.generated.ts @@ -43,6 +43,7 @@ declare function get_current_ClassDeclaration_LazyLoadedDataObject(): declare function use_old_ClassDeclaration_LazyLoadedDataObject( use: TypeOnly): void; use_old_ClassDeclaration_LazyLoadedDataObject( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_LazyLoadedDataObject()); /* diff --git a/packages/framework/dds-interceptions/package.json b/packages/framework/dds-interceptions/package.json index 8b824bdee86f..126655d27506 100644 --- a/packages/framework/dds-interceptions/package.json +++ b/packages/framework/dds-interceptions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/dds-interceptions", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed Data Structures that support an interception callback", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts b/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts index e986a0d03766..9fc1fdc80192 100644 --- a/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts +++ b/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts @@ -199,50 +199,15 @@ export function createSharedStringWithInterception( }); }; - /** - * Annotates the marker with the provided properties and calls the callback on concensus. - * - * @param marker - The marker to annotate - * @param props - The properties to annotate the marker with - * @param consensusCallback - The callback called when consensus is reached - */ - sharedStringWithInterception.annotateMarkerNotifyConsensus = ( - marker: MergeTree.Marker, - props: MergeTree.PropertySet, - callback: (m: MergeTree.Marker) => void, - ) => { - // Wrapper methods should not be called from the interception callback as this will lead to - // infinite recursion. - assert( - executingCallback === false, - 0x0c6 /* "Interception wrapper methods called recursively from the interception callback" */, - ); - - context.containerRuntime.orderSequentially(() => { - executingCallback = true; - try { - sharedString.annotateMarkerNotifyConsensus( - marker, - propertyInterceptionCallback(props), - callback, - ); - } finally { - executingCallback = false; - } - }); - }; - /** * Annotates the marker with the provided properties. * * @param marker - The marker to annotate * @param props - The properties to annotate the marker with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. */ sharedStringWithInterception.annotateMarker = ( marker: MergeTree.Marker, props: MergeTree.PropertySet, - combiningOp?: MergeTree.ICombiningOp, ) => { // Wrapper methods should not be called from the interception callback as this will lead to // infinite recursion. @@ -254,11 +219,7 @@ export function createSharedStringWithInterception( context.containerRuntime.orderSequentially(() => { executingCallback = true; try { - sharedString.annotateMarker( - marker, - propertyInterceptionCallback(props), - combiningOp, - ); + sharedString.annotateMarker(marker, propertyInterceptionCallback(props)); } finally { executingCallback = false; } @@ -271,14 +232,12 @@ export function createSharedStringWithInterception( * @param start - The inclusive start position of the range to annotate * @param end - The exclusive end position of the range to annotate * @param props - The properties to annotate the range with - * @param combiningOp - Optional. Specifies how to combine values for the property, such as "incr" for increment. * */ sharedStringWithInterception.annotateRange = ( start: number, end: number, props: MergeTree.PropertySet, - combiningOp?: MergeTree.ICombiningOp, ) => { // Wrapper methods should not be called from the interception callback as this will lead to // infinite recursion. @@ -290,12 +249,7 @@ export function createSharedStringWithInterception( context.containerRuntime.orderSequentially(() => { executingCallback = true; try { - sharedString.annotateRange( - start, - end, - propertyInterceptionCallback(props), - combiningOp, - ); + sharedString.annotateRange(start, end, propertyInterceptionCallback(props)); } finally { executingCallback = false; } diff --git a/packages/framework/fluid-framework/api-report/fluid-framework.api.md b/packages/framework/fluid-framework/api-report/fluid-framework.api.md index 4195ce00a8de..4376eff6c04b 100644 --- a/packages/framework/fluid-framework/api-report/fluid-framework.api.md +++ b/packages/framework/fluid-framework/api-report/fluid-framework.api.md @@ -11,9 +11,7 @@ import { ContainerSchema } from '@fluidframework/fluid-static'; import { DataObjectClass } from '@fluidframework/fluid-static'; import { DeserializeCallback } from '@fluidframework/sequence'; import { DirectoryFactory } from '@fluidframework/map'; -import { DOProviderContainerRuntimeFactory } from '@fluidframework/fluid-static'; import { DriverErrorType } from '@fluidframework/driver-definitions'; -import { FluidContainer } from '@fluidframework/fluid-static'; import { getTextAndMarkers } from '@fluidframework/sequence'; import { IConnection } from '@fluidframework/fluid-static'; import { ICriticalContainerError } from '@fluidframework/container-definitions'; @@ -74,7 +72,6 @@ import { SequenceEvent } from '@fluidframework/sequence'; import { SequenceInterval } from '@fluidframework/sequence'; import { SequenceMaintenanceEvent } from '@fluidframework/sequence'; import { SerializedIntervalDelta } from '@fluidframework/sequence'; -import { ServiceAudience } from '@fluidframework/fluid-static'; import { SharedDirectory } from '@fluidframework/map'; import { SharedIntervalCollection } from '@fluidframework/sequence'; import { SharedIntervalCollectionFactory } from '@fluidframework/sequence'; @@ -101,12 +98,8 @@ export { DeserializeCallback } export { DirectoryFactory } -export { DOProviderContainerRuntimeFactory } - export { DriverErrorType } -export { FluidContainer } - export { getTextAndMarkers } export { IConnection } @@ -227,8 +220,6 @@ export { SequenceMaintenanceEvent } export { SerializedIntervalDelta } -export { ServiceAudience } - export { SharedDirectory } export { SharedIntervalCollection } diff --git a/packages/framework/fluid-framework/package.json b/packages/framework/fluid-framework/package.json index 0a7ae2cf3185..84930be2e504 100644 --- a/packages/framework/fluid-framework/package.json +++ b/packages/framework/fluid-framework/package.json @@ -1,6 +1,6 @@ { "name": "fluid-framework", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "The main entry point into Fluid Framework public packages", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/fluid-framework/src/index.ts b/packages/framework/fluid-framework/src/index.ts index 202dd5223327..2c6591534cdd 100644 --- a/packages/framework/fluid-framework/src/index.ts +++ b/packages/framework/fluid-framework/src/index.ts @@ -32,11 +32,6 @@ export type { MemberChangedListener, SharedObjectClass, } from "@fluidframework/fluid-static"; -export { - DOProviderContainerRuntimeFactory, - FluidContainer, - ServiceAudience, -} from "@fluidframework/fluid-static"; export type { IDirectory, IDirectoryClearOperation, diff --git a/packages/framework/fluid-static/api-report/fluid-static.api.md b/packages/framework/fluid-static/api-report/fluid-static.api.md index 0d0ef7a5f11f..6f3e3c2e0a06 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.api.md @@ -5,20 +5,15 @@ ```ts import { AttachState } from '@fluidframework/container-definitions'; -import { BaseContainerRuntimeFactory } from '@fluidframework/aqueduct'; import { ConnectionState } from '@fluidframework/container-definitions'; -import { IAudience } from '@fluidframework/container-definitions'; import { IChannelFactory } from '@fluidframework/datastore-definitions'; import { IClient } from '@fluidframework/protocol-definitions'; import { IContainer } from '@fluidframework/container-definitions'; -import { IContainerRuntime } from '@fluidframework/container-runtime-definitions'; import { ICriticalContainerError } from '@fluidframework/container-definitions'; import { IEvent } from '@fluidframework/core-interfaces'; import { IEventProvider } from '@fluidframework/core-interfaces'; -import { IFluidDataStoreFactory } from '@fluidframework/runtime-definitions'; import { IFluidLoadable } from '@fluidframework/core-interfaces'; import { IRuntimeFactory } from '@fluidframework/container-definitions'; -import { TypedEventEmitter } from '@fluid-internal/client-utils'; // @alpha export interface ContainerSchema { @@ -45,32 +40,11 @@ export function createServiceAudience(props: { // @alpha export type DataObjectClass = { - readonly factory: IFluidDataStoreFactory; + readonly factory: { + IFluidDataStoreFactory: DataObjectClass["factory"]; + }; } & LoadableObjectCtor; -// @internal @deprecated -export class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { - constructor(schema: ContainerSchema); - // (undocumented) - protected containerInitializingFirstTime(runtime: IContainerRuntime): Promise; -} - -// @internal @deprecated -export class FluidContainer extends TypedEventEmitter implements IFluidContainer { - constructor(container: IContainer, rootDataObject: IRootDataObject); - attach(): Promise; - get attachState(): AttachState; - connect(): Promise; - get connectionState(): ConnectionState; - create(objectClass: LoadableObjectClass): Promise; - disconnect(): Promise; - dispose(): void; - get disposed(): boolean; - get initialObjects(): InitialObjects; - readonly INTERNAL_CONTAINER_DO_NOT_USE?: () => IContainer; - get isDirty(): boolean; -} - // @alpha export interface IConnection { id: string; @@ -114,7 +88,7 @@ export type InitialObjects = { // @internal (undocumented) export interface IProvideRootDataObject { // (undocumented) - readonly IRootDataObject?: IRootDataObject; + readonly IRootDataObject: IRootDataObject; } // @internal @@ -159,19 +133,6 @@ export type Myself = M & { currentConnection: string; }; -// @internal @deprecated -export abstract class ServiceAudience extends TypedEventEmitter> implements IServiceAudience { - constructor( - container: IContainer); - protected readonly audience: IAudience; - protected readonly container: IContainer; - protected abstract createServiceMember(audienceMember: IClient): M; - getMembers(): Map; - getMyself(): Myself | undefined; - protected lastMembers: Map; - protected shouldIncludeAsMember(member: IClient): boolean; -} - // @alpha export type SharedObjectClass = { readonly getFactory: () => IChannelFactory; diff --git a/packages/framework/fluid-static/package.json b/packages/framework/fluid-static/package.json index 231e333cda4c..d2b092c8f43f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A tool to enable consumption of Fluid Data Objects without requiring custom container code.", "homepage": "https://fluidframework.com", "repository": { @@ -68,6 +68,7 @@ "@fluidframework/aqueduct": "workspace:~", "@fluidframework/container-definitions": "workspace:~", "@fluidframework/container-loader": "workspace:~", + "@fluidframework/container-runtime": "workspace:~", "@fluidframework/container-runtime-definitions": "workspace:~", "@fluidframework/core-interfaces": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", @@ -115,6 +116,22 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedClassDeclaration_DOProviderContainerRuntimeFactory": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_FluidContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_ServiceAudience": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_IRootDataObject": { + "forwardCompat": false + } + } } } diff --git a/packages/framework/fluid-static/src/fluidContainer.ts b/packages/framework/fluid-static/src/fluidContainer.ts index 153a6767779e..3a45c49dd336 100644 --- a/packages/framework/fluid-static/src/fluidContainer.ts +++ b/packages/framework/fluid-static/src/fluidContainer.ts @@ -234,7 +234,7 @@ export function createFluidContainer< * @deprecated use {@link createFluidContainer} and {@link IFluidContainer} instead * @internal */ -export class FluidContainer +class FluidContainer extends TypedEventEmitter implements IFluidContainer { diff --git a/packages/framework/fluid-static/src/index.ts b/packages/framework/fluid-static/src/index.ts index 63af6bdfd22d..d83c837abc27 100644 --- a/packages/framework/fluid-static/src/index.ts +++ b/packages/framework/fluid-static/src/index.ts @@ -10,17 +10,13 @@ */ export { - FluidContainer, createFluidContainer, IFluidContainer, IFluidContainerEvents, InitialObjects, } from "./fluidContainer"; -export { - DOProviderContainerRuntimeFactory, - createDOProviderContainerRuntimeFactory, -} from "./rootDataObject"; -export { ServiceAudience, createServiceAudience } from "./serviceAudience"; +export { createDOProviderContainerRuntimeFactory } from "./rootDataObject"; +export { createServiceAudience } from "./serviceAudience"; export { ContainerSchema, DataObjectClass, diff --git a/packages/framework/fluid-static/src/rootDataObject.ts b/packages/framework/fluid-static/src/rootDataObject.ts index 3ce542ffff86..032043ef0362 100644 --- a/packages/framework/fluid-static/src/rootDataObject.ts +++ b/packages/framework/fluid-static/src/rootDataObject.ts @@ -6,23 +6,27 @@ import { BaseContainerRuntimeFactory, DataObject, DataObjectFactory, - // eslint-disable-next-line import/no-deprecated - defaultRouteRequestHandler, } from "@fluidframework/aqueduct"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IFluidLoadable } from "@fluidframework/core-interfaces"; +import { IFluidLoadable, IRequest } from "@fluidframework/core-interfaces"; import { FlushMode } from "@fluidframework/runtime-definitions"; import { IRuntimeFactory } from "@fluidframework/container-definitions"; +import { RequestParser } from "@fluidframework/runtime-utils"; +import { ContainerRuntime } from "@fluidframework/container-runtime"; import { ContainerSchema, - DataObjectClass, IRootDataObject, LoadableObjectClass, LoadableObjectClassRecord, LoadableObjectRecord, SharedObjectClass, } from "./types"; -import { isDataObjectClass, isSharedObjectClass, parseDataObjectsFromSharedObjects } from "./utils"; +import { + InternalDataObjectClass, + isDataObjectClass, + isSharedObjectClass, + parseDataObjectsFromSharedObjects, +} from "./utils"; /** * Input props for {@link RootDataObject.initializingFirstTime}. @@ -123,7 +127,7 @@ class RootDataObject } private async createDataObject( - dataObjectClass: DataObjectClass, + dataObjectClass: InternalDataObjectClass, ): Promise { const factory = dataObjectClass.factory; const packagePath = [...this.context.packagePath, factory.type]; @@ -159,10 +163,10 @@ export function createDOProviderContainerRuntimeFactory(props: { * * This data object is dynamically customized (registry and initial objects) based on the schema provided. * to the container runtime factory. - * @deprecated use {@link createDOProviderContainerRuntimeFactory} instead + * * @internal */ -export class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { +class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { private readonly rootDataObjectFactory: DataObjectFactory< RootDataObject, { @@ -181,21 +185,32 @@ export class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFacto {}, registryEntries, ); + const provideEntryPoint = async (containerRuntime: IContainerRuntime) => { + const entryPoint = + await containerRuntime.getAliasedDataStoreEntryPoint(rootDataStoreId); + if (entryPoint === undefined) { + throw new Error(`default dataStore [${rootDataStoreId}] must exist`); + } + return entryPoint.get(); + }; + const getDefaultObject = async (request: IRequest, runtime: IContainerRuntime) => { + const parser = RequestParser.create(request); + if (parser.pathParts.length === 0) { + // This cast is safe as ContainerRuntime.loadRuntime is called in the base class + return (runtime as ContainerRuntime).resolveHandle({ + url: `/${rootDataStoreId}${parser.query}`, + headers: request.headers, + }); + } + return undefined; // continue search + }; super({ registryEntries: [rootDataObjectFactory.registryEntry], - // eslint-disable-next-line import/no-deprecated - requestHandlers: [defaultRouteRequestHandler(rootDataStoreId)], + requestHandlers: [getDefaultObject], // temporary workaround to disable message batching until the message batch size issue is resolved // resolution progress is tracked by the Feature 465 work item in AzDO runtimeOptions: { flushMode: FlushMode.Immediate }, - provideEntryPoint: async (containerRuntime: IContainerRuntime) => { - const entryPoint = - await containerRuntime.getAliasedDataStoreEntryPoint(rootDataStoreId); - if (entryPoint === undefined) { - throw new Error(`default dataStore [${rootDataStoreId}] must exist`); - } - return entryPoint.get(); - }, + provideEntryPoint, }); this.rootDataObjectFactory = rootDataObjectFactory; this.initialObjects = schema.initialObjects; diff --git a/packages/framework/fluid-static/src/serviceAudience.ts b/packages/framework/fluid-static/src/serviceAudience.ts index c034e0f6a85b..c225939975c1 100644 --- a/packages/framework/fluid-static/src/serviceAudience.ts +++ b/packages/framework/fluid-static/src/serviceAudience.ts @@ -15,13 +15,7 @@ export function createServiceAudience(props: { container: IContainer; createServiceMember: (audienceMember: IClient) => M; }): IServiceAudience { - // todo: once ServiceAudience isn't exported, we can remove abstract from it, and just use that here - const c = class NewServiceAudience extends ServiceAudience { - protected createServiceMember(audienceMember: IClient) { - return props.createServiceMember(audienceMember); - } - }; - return new c(props.container); + return new ServiceAudience(props.container, props.createServiceMember); } /** @@ -33,17 +27,16 @@ export function createServiceAudience(props: { * the user and client details returned in {@link IMember}. * * @typeParam M - A service-specific {@link IMember} implementation. - * @deprecated use {@link createServiceAudience} and {@link IServiceAudience} instead * @internal */ -export abstract class ServiceAudience +class ServiceAudience extends TypedEventEmitter> implements IServiceAudience { /** * Audience object which includes all the existing members of the {@link IFluidContainer | container}. */ - protected readonly audience: IAudience; + private readonly audience: IAudience; /** * Retain the most recent member list. @@ -62,13 +55,14 @@ export abstract class ServiceAudience * every `addMember` event. It is mapped `clientId` to `M` to be better work with what the {@link IServiceAudience} * events provide. */ - protected lastMembers = new Map(); + private lastMembers = new Map(); constructor( /** * Fluid Container to read the audience from. */ - protected readonly container: IContainer, + private readonly container: IContainer, + private readonly createServiceMember: (audienceMember: IClient) => M, ) { super(); this.audience = container.audience; @@ -95,13 +89,6 @@ export abstract class ServiceAudience this.container.on("connected", () => this.emit("membersChanged")); } - /** - * Provides ability for inheriting class to modify/extend the audience object. - * - * @param audienceMember - Record of a specific audience member. - */ - protected abstract createServiceMember(audienceMember: IClient): M; - /** * {@inheritDoc IServiceAudience.getMembers} */ @@ -170,7 +157,7 @@ export abstract class ServiceAudience * * @param member - Member to be included/omitted. */ - protected shouldIncludeAsMember(member: IClient): boolean { + private shouldIncludeAsMember(member: IClient): boolean { // Include only human members return member.details.capabilities.interactive; } diff --git a/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts b/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts index 4df249340ae6..e5d71fc58103 100644 --- a/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts +++ b/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts @@ -48,26 +48,14 @@ use_old_InterfaceDeclaration_ContainerSchema( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_DOProviderContainerRuntimeFactory": {"forwardCompat": false} +* "RemovedClassDeclaration_DOProviderContainerRuntimeFactory": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_DOProviderContainerRuntimeFactory(): - TypeOnly; -declare function use_current_ClassDeclaration_DOProviderContainerRuntimeFactory( - use: TypeOnly): void; -use_current_ClassDeclaration_DOProviderContainerRuntimeFactory( - get_old_ClassDeclaration_DOProviderContainerRuntimeFactory()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_DOProviderContainerRuntimeFactory": {"backCompat": false} +* "RemovedClassDeclaration_DOProviderContainerRuntimeFactory": {"backCompat": false} */ -declare function get_current_ClassDeclaration_DOProviderContainerRuntimeFactory(): - TypeOnly; -declare function use_old_ClassDeclaration_DOProviderContainerRuntimeFactory( - use: TypeOnly): void; -use_old_ClassDeclaration_DOProviderContainerRuntimeFactory( - get_current_ClassDeclaration_DOProviderContainerRuntimeFactory()); /* * Validate forward compat by using old type in place of current type @@ -96,26 +84,14 @@ use_old_TypeAliasDeclaration_DataObjectClass( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_FluidContainer": {"forwardCompat": false} +* "RemovedClassDeclaration_FluidContainer": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_FluidContainer(): - TypeOnly; -declare function use_current_ClassDeclaration_FluidContainer( - use: TypeOnly): void; -use_current_ClassDeclaration_FluidContainer( - get_old_ClassDeclaration_FluidContainer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_FluidContainer": {"backCompat": false} +* "RemovedClassDeclaration_FluidContainer": {"backCompat": false} */ -declare function get_current_ClassDeclaration_FluidContainer(): - TypeOnly; -declare function use_old_ClassDeclaration_FluidContainer( - use: TypeOnly): void; -use_old_ClassDeclaration_FluidContainer( - get_current_ClassDeclaration_FluidContainer()); /* * Validate forward compat by using old type in place of current type @@ -223,6 +199,7 @@ declare function get_old_InterfaceDeclaration_IRootDataObject(): declare function use_current_InterfaceDeclaration_IRootDataObject( use: TypeOnly): void; use_current_InterfaceDeclaration_IRootDataObject( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_IRootDataObject()); /* @@ -432,26 +409,14 @@ use_old_TypeAliasDeclaration_Myself( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_ServiceAudience": {"forwardCompat": false} +* "RemovedClassDeclaration_ServiceAudience": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_ServiceAudience(): - TypeOnly; -declare function use_current_ClassDeclaration_ServiceAudience( - use: TypeOnly): void; -use_current_ClassDeclaration_ServiceAudience( - get_old_ClassDeclaration_ServiceAudience()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_ServiceAudience": {"backCompat": false} +* "RemovedClassDeclaration_ServiceAudience": {"backCompat": false} */ -declare function get_current_ClassDeclaration_ServiceAudience(): - TypeOnly; -declare function use_old_ClassDeclaration_ServiceAudience( - use: TypeOnly): void; -use_old_ClassDeclaration_ServiceAudience( - get_current_ClassDeclaration_ServiceAudience()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/framework/fluid-static/src/types.ts b/packages/framework/fluid-static/src/types.ts index 0d1ab424d163..60617d180448 100644 --- a/packages/framework/fluid-static/src/types.ts +++ b/packages/framework/fluid-static/src/types.ts @@ -5,7 +5,6 @@ import { IEvent, IEventProvider, IFluidLoadable } from "@fluidframework/core-interfaces"; import { IChannelFactory } from "@fluidframework/datastore-definitions"; -import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; /** * A mapping of string identifiers to instantiated `DataObject`s or `SharedObject`s. @@ -38,7 +37,7 @@ export type LoadableObjectClass = * @alpha */ export type DataObjectClass = { - readonly factory: IFluidDataStoreFactory; + readonly factory: { IFluidDataStoreFactory: DataObjectClass["factory"] }; } & LoadableObjectCtor; /** @@ -107,7 +106,7 @@ export interface ContainerSchema { * @internal */ export interface IProvideRootDataObject { - readonly IRootDataObject?: IRootDataObject; + readonly IRootDataObject: IRootDataObject; } /** diff --git a/packages/framework/fluid-static/src/utils.ts b/packages/framework/fluid-static/src/utils.ts index 6cc85eda0241..9dcf7ab5e1e2 100644 --- a/packages/framework/fluid-static/src/utils.ts +++ b/packages/framework/fluid-static/src/utils.ts @@ -4,14 +4,31 @@ */ import { IChannelFactory } from "@fluidframework/datastore-definitions"; -import { NamedFluidDataStoreRegistryEntry } from "@fluidframework/runtime-definitions"; +import { + IFluidDataStoreFactory, + NamedFluidDataStoreRegistryEntry, +} from "@fluidframework/runtime-definitions"; +import { IFluidLoadable } from "@fluidframework/core-interfaces"; import { ContainerSchema, DataObjectClass, LoadableObjectClass, SharedObjectClass } from "./types"; +/** + * An internal type used by the internal type guard isDataObjectClass to cast a + * DataObjectClass to a type that is strongly coupled to IFluidDataStoreFactory. + * Unlike the external and exported type DataObjectClass which is + * weakly coupled to the IFluidDataStoreFactory to prevent leaking internals. + */ +export type InternalDataObjectClass = DataObjectClass & + Record<"factory", IFluidDataStoreFactory>; + /** * Runtime check to determine if a class is a DataObject type */ -export const isDataObjectClass = (obj: any): obj is DataObjectClass => { - return obj?.factory !== undefined; +export const isDataObjectClass = (obj: any): obj is InternalDataObjectClass => { + const maybe: Partial> | undefined = obj; + return ( + maybe?.factory?.IFluidDataStoreFactory !== undefined && + maybe?.factory?.IFluidDataStoreFactory === maybe?.factory + ); }; /** diff --git a/packages/framework/oldest-client-observer/package.json b/packages/framework/oldest-client-observer/package.json index f1e21630a3c4..248f3219e747 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Data object to determine if the local client is the oldest amongst connected clients", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/request-handler/api-report/request-handler.api.md b/packages/framework/request-handler/api-report/request-handler.api.md index a12485c1b0b8..7578afe57de8 100644 --- a/packages/framework/request-handler/api-report/request-handler.api.md +++ b/packages/framework/request-handler/api-report/request-handler.api.md @@ -4,11 +4,7 @@ ```ts -import { FluidObject } from '@fluidframework/core-interfaces'; import { IContainerRuntime } from '@fluidframework/container-runtime-definitions'; -import { IContainerRuntimeBase } from '@fluidframework/runtime-definitions'; -import { IFluidHandle } from '@fluidframework/core-interfaces'; -import { IFluidLoadable } from '@fluidframework/core-interfaces'; import { IRequest } from '@fluidframework/core-interfaces'; import { IResponse } from '@fluidframework/core-interfaces'; import { RequestParser } from '@fluidframework/runtime-utils'; @@ -16,30 +12,9 @@ import { RequestParser } from '@fluidframework/runtime-utils'; // @internal @deprecated (undocumented) export function buildRuntimeRequestHandler(...handlers: RuntimeRequestHandler[]): (request: IRequest, runtime: IContainerRuntime) => Promise; -// @internal @deprecated (undocumented) -export const createFluidObjectResponse: (fluidObject: FluidObject) => { - status: 200; - mimeType: "fluid/object"; - value: FluidObject; -}; - -// @internal @deprecated (undocumented) -export function handleFromLegacyUri(uri: string, runtime: IContainerRuntimeBase): IFluidHandle; - -// @internal @deprecated -export const rootDataStoreRequestHandler: (request: IRequest, runtime: IContainerRuntime) => Promise; - // @alpha @deprecated export type RuntimeRequestHandler = (request: RequestParser, runtime: IContainerRuntime) => Promise; -// @internal @deprecated -export class RuntimeRequestHandlerBuilder { - // (undocumented) - handleRequest(request: IRequest, runtime: IContainerRuntime): Promise; - // (undocumented) - pushHandler(...handlers: RuntimeRequestHandler[]): void; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/framework/request-handler/package.json b/packages/framework/request-handler/package.json index 2a1b89926d28..72a16d9d822a 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A simple request handling library for Fluid Framework", "homepage": "https://fluidframework.com", "repository": { @@ -110,6 +110,23 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedVariableDeclaration_createFluidObjectResponse": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_rootDataStoreRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_handleFromLegacyUri": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_RuntimeRequestHandlerBuilder": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/framework/request-handler/src/index.ts b/packages/framework/request-handler/src/index.ts index 5ee987482f30..0f5823d78354 100644 --- a/packages/framework/request-handler/src/index.ts +++ b/packages/framework/request-handler/src/index.ts @@ -3,13 +3,5 @@ * Licensed under the MIT License. */ -export { - createFluidObjectResponse, - handleFromLegacyUri, - rootDataStoreRequestHandler, - RuntimeRequestHandler, -} from "./requestHandlers"; -export { - buildRuntimeRequestHandler, - RuntimeRequestHandlerBuilder, -} from "./runtimeRequestHandlerBuilder"; +export { RuntimeRequestHandler } from "./requestHandlers"; +export { buildRuntimeRequestHandler } from "./runtimeRequestHandlerBuilder"; diff --git a/packages/framework/request-handler/src/requestHandlers.ts b/packages/framework/request-handler/src/requestHandlers.ts index db9a5c2352b6..a0dedd12dc34 100644 --- a/packages/framework/request-handler/src/requestHandlers.ts +++ b/packages/framework/request-handler/src/requestHandlers.ts @@ -3,18 +3,8 @@ * Licensed under the MIT License. */ -import { assert } from "@fluidframework/core-utils"; -import { - IResponse, - IRequest, - IFluidHandle, - IFluidLoadable, - FluidObject, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, -} from "@fluidframework/core-interfaces"; +import { IResponse } from "@fluidframework/core-interfaces"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { RequestParser } from "@fluidframework/runtime-utils"; /** @@ -22,93 +12,11 @@ import { RequestParser } from "@fluidframework/runtime-utils"; * if it does not apply. These handlers are called in series, so there may be other handlers before or after. * A handler should only return error if the request is for a route the handler owns, and there is a problem with * the route, or fulling the specific request. - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md + * @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md + * * @alpha */ export type RuntimeRequestHandler = ( request: RequestParser, runtime: IContainerRuntime, ) => Promise; - -/** - * A request handler to expose access to all root data stores in the container by id. - * @param request - the request for the root data store. The first path part must be the data store's ID. - * @param runtime - the container runtime - * @returns the result of the request - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export const rootDataStoreRequestHandler = async ( - request: IRequest, - runtime: IContainerRuntime, -) => { - const requestParser = RequestParser.create(request); - const id = requestParser.pathParts[0]; - const wait = typeof request.headers?.wait === "boolean" ? request.headers.wait : undefined; - // eslint-disable-next-line import/no-deprecated - let rootDataStore: IFluidRouter; - try { - // getRootDataStore currently throws if the data store is not found - rootDataStore = await runtime.getRootDataStore(id, wait); - } catch (error) { - return undefined; // continue search - } - try { - return await rootDataStore.IFluidRouter.request(requestParser.createSubRequest(1)); - } catch (error) { - return { status: 500, mimeType: "fluid/object", value: error }; - } -}; - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export const createFluidObjectResponse = ( - fluidObject: FluidObject, -): { status: 200; mimeType: "fluid/object"; value: FluidObject } => { - return { status: 200, mimeType: "fluid/object", value: fluidObject }; -}; - -class LegacyUriHandle implements IFluidHandle { - public readonly isAttached = true; - - public get IFluidHandle(): IFluidHandle { - return this; - } - - public constructor( - public readonly absolutePath, - public readonly runtime: IContainerRuntimeBase, - ) {} - - public attachGraph() { - assert(false, 0x0ca /* "Trying to use legacy graph attach!" */); - } - - public async get(): Promise { - const response = await this.runtime.IFluidHandleContext.resolveHandle({ - url: this.absolutePath, - }); - if (response.status === 200 && response.mimeType === "fluid/object") { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.value; - } - throw new Error(`Failed to resolve container path ${this.absolutePath}`); - } - - public bind(handle: IFluidHandle) { - throw new Error("Cannot bind to LegacyUriHandle"); - } -} - -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export function handleFromLegacyUri( - uri: string, - runtime: IContainerRuntimeBase, -): IFluidHandle { - return new LegacyUriHandle(uri, runtime); -} diff --git a/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts b/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts index 235750ec7327..8a3ad2fd3e32 100644 --- a/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts +++ b/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts @@ -12,11 +12,8 @@ import { RuntimeRequestHandler } from "./requestHandlers"; /** * The RuntimeRequestHandlerBuilder creates a runtime request handler based on request handlers. * The provided handlers sequentially applied until one is able to satisfy the request. - * - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal */ -export class RuntimeRequestHandlerBuilder { +class RuntimeRequestHandlerBuilder { // eslint-disable-next-line import/no-deprecated private readonly handlers: RuntimeRequestHandler[] = []; @@ -40,7 +37,8 @@ export class RuntimeRequestHandlerBuilder { } /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md + * @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md + * * @internal */ // eslint-disable-next-line import/no-deprecated diff --git a/examples/data-objects/shared-text/prettier.config.cjs b/packages/framework/request-handler/src/test/requestHandler.spec.ts similarity index 51% rename from examples/data-objects/shared-text/prettier.config.cjs rename to packages/framework/request-handler/src/test/requestHandler.spec.ts index d4870022599f..def818334195 100644 --- a/examples/data-objects/shared-text/prettier.config.cjs +++ b/packages/framework/request-handler/src/test/requestHandler.spec.ts @@ -3,6 +3,5 @@ * Licensed under the MIT License. */ -module.exports = { - ...require("@fluidframework/build-common/prettier.config.cjs"), -}; +// Build pipeline breaks without this file ("No test files found") +describe("requestHandler-placeholder", () => {}); diff --git a/packages/framework/request-handler/src/test/requestHandlers.spec.ts b/packages/framework/request-handler/src/test/requestHandlers.spec.ts deleted file mode 100644 index 4c4c1d2dd71f..000000000000 --- a/packages/framework/request-handler/src/test/requestHandlers.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { strict as assert } from "assert"; -import { IRequest, IResponse, IFluidRouter } from "@fluidframework/core-interfaces"; -import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IFluidDataStoreChannel } from "@fluidframework/runtime-definitions"; -import { RequestParser, create404Response } from "@fluidframework/runtime-utils"; -import { createFluidObjectResponse, rootDataStoreRequestHandler } from "../requestHandlers"; - -class MockRuntime { - public get IFluidHandleContext() { - return this; - } - - public async getRootDataStore(id, wait): Promise { - if (id === "objectId") { - const router: any = { - request: async (request: IRequest) => { - if (request.url === "" || request.url === "/route") { - return createFluidObjectResponse({ route: request.url }); - } - return create404Response(request); - }, - }; - router.IFluidRouter = router; - return router as IFluidDataStoreChannel; - } - - assert(wait !== true); - throw new Error("No object"); - } - - public async resolveHandle(request: IRequest) { - const requestParser = RequestParser.create(request); - - if (requestParser.pathParts.length > 0) { - const wait = - typeof request.headers?.wait === "boolean" ? request.headers.wait : undefined; - - const fluidObject = await this.getRootDataStore(requestParser.pathParts[0], wait); - const subRequest = requestParser.createSubRequest(1); - return fluidObject.request(subRequest); - } - return create404Response(request); - } -} - -async function assertRejected(p: Promise) { - try { - const res = await p; - assert(res === undefined || res.status === 404, "not rejected"); - } catch (err) {} -} - -// TODO: Remove this file when rootDataStoreRequestHandler is removed AB#4991 -describe("RequestParser", () => { - describe("rootDataStoreRequestHandler", () => { - const runtime = new MockRuntime() as any as IContainerRuntime; - - it("Empty request", async () => { - const requestParser = RequestParser.create({ url: "/" }); - const response = await rootDataStoreRequestHandler(requestParser, runtime); - assert.equal(response?.status, undefined); - }); - - it("Data store request without wait", async () => { - const requestParser = RequestParser.create({ url: "/nonExistingUri" }); - const responseP = rootDataStoreRequestHandler(requestParser, runtime); - await assertRejected(responseP); - }); - - it("Data store request with wait", async () => { - const requestParser = RequestParser.create({ - url: "/nonExistingUri", - headers: { wait: true }, - }); - const responseP = rootDataStoreRequestHandler(requestParser, runtime); - await assertRejected(responseP); - }); - - it("Data store request with sub route", async () => { - const requestParser = RequestParser.create({ - url: "/objectId/route", - headers: { wait: true }, - }); - const response = await rootDataStoreRequestHandler(requestParser, runtime); - assert.equal(response?.status, 200); - assert.equal(response?.value.route, "/route"); - }); - - it("Data store request with non-existing sub route", async () => { - const requestParser = RequestParser.create({ - url: "/objectId/doesNotExist", - headers: { wait: true }, - }); - const responseP = rootDataStoreRequestHandler(requestParser, runtime); - await assertRejected(responseP); - }); - }); -}); diff --git a/packages/framework/request-handler/src/test/types/validateRequestHandlerPrevious.generated.ts b/packages/framework/request-handler/src/test/types/validateRequestHandlerPrevious.generated.ts index eda62ec07026..1204d568175f 100644 --- a/packages/framework/request-handler/src/test/types/validateRequestHandlerPrevious.generated.ts +++ b/packages/framework/request-handler/src/test/types/validateRequestHandlerPrevious.generated.ts @@ -48,26 +48,14 @@ use_old_TypeAliasDeclaration_RuntimeRequestHandler( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_RuntimeRequestHandlerBuilder": {"forwardCompat": false} +* "RemovedClassDeclaration_RuntimeRequestHandlerBuilder": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_RuntimeRequestHandlerBuilder(): - TypeOnly; -declare function use_current_ClassDeclaration_RuntimeRequestHandlerBuilder( - use: TypeOnly): void; -use_current_ClassDeclaration_RuntimeRequestHandlerBuilder( - get_old_ClassDeclaration_RuntimeRequestHandlerBuilder()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_RuntimeRequestHandlerBuilder": {"backCompat": false} +* "RemovedClassDeclaration_RuntimeRequestHandlerBuilder": {"backCompat": false} */ -declare function get_current_ClassDeclaration_RuntimeRequestHandlerBuilder(): - TypeOnly; -declare function use_old_ClassDeclaration_RuntimeRequestHandlerBuilder( - use: TypeOnly): void; -use_old_ClassDeclaration_RuntimeRequestHandlerBuilder( - get_current_ClassDeclaration_RuntimeRequestHandlerBuilder()); /* * Validate forward compat by using old type in place of current type @@ -96,71 +84,35 @@ use_old_FunctionDeclaration_buildRuntimeRequestHandler( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_createFluidObjectResponse": {"forwardCompat": false} +* "RemovedVariableDeclaration_createFluidObjectResponse": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_createFluidObjectResponse(): - TypeOnly; -declare function use_current_VariableDeclaration_createFluidObjectResponse( - use: TypeOnly): void; -use_current_VariableDeclaration_createFluidObjectResponse( - get_old_VariableDeclaration_createFluidObjectResponse()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_createFluidObjectResponse": {"backCompat": false} +* "RemovedVariableDeclaration_createFluidObjectResponse": {"backCompat": false} */ -declare function get_current_VariableDeclaration_createFluidObjectResponse(): - TypeOnly; -declare function use_old_VariableDeclaration_createFluidObjectResponse( - use: TypeOnly): void; -use_old_VariableDeclaration_createFluidObjectResponse( - get_current_VariableDeclaration_createFluidObjectResponse()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_handleFromLegacyUri": {"forwardCompat": false} +* "RemovedFunctionDeclaration_handleFromLegacyUri": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_handleFromLegacyUri(): - TypeOnly; -declare function use_current_FunctionDeclaration_handleFromLegacyUri( - use: TypeOnly): void; -use_current_FunctionDeclaration_handleFromLegacyUri( - get_old_FunctionDeclaration_handleFromLegacyUri()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_handleFromLegacyUri": {"backCompat": false} +* "RemovedFunctionDeclaration_handleFromLegacyUri": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_handleFromLegacyUri(): - TypeOnly; -declare function use_old_FunctionDeclaration_handleFromLegacyUri( - use: TypeOnly): void; -use_old_FunctionDeclaration_handleFromLegacyUri( - get_current_FunctionDeclaration_handleFromLegacyUri()); /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_rootDataStoreRequestHandler": {"forwardCompat": false} +* "RemovedVariableDeclaration_rootDataStoreRequestHandler": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_rootDataStoreRequestHandler(): - TypeOnly; -declare function use_current_VariableDeclaration_rootDataStoreRequestHandler( - use: TypeOnly): void; -use_current_VariableDeclaration_rootDataStoreRequestHandler( - get_old_VariableDeclaration_rootDataStoreRequestHandler()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_rootDataStoreRequestHandler": {"backCompat": false} +* "RemovedVariableDeclaration_rootDataStoreRequestHandler": {"backCompat": false} */ -declare function get_current_VariableDeclaration_rootDataStoreRequestHandler(): - TypeOnly; -declare function use_old_VariableDeclaration_rootDataStoreRequestHandler( - use: TypeOnly): void; -use_old_VariableDeclaration_rootDataStoreRequestHandler( - get_current_VariableDeclaration_rootDataStoreRequestHandler()); diff --git a/packages/framework/synthesize/package.json b/packages/framework/synthesize/package.json index ab5fbad5cdc4..91eb971c0f74 100644 --- a/packages/framework/synthesize/package.json +++ b/packages/framework/synthesize/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/synthesize", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A library for synthesizing scope objects.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/synthesize/src/test/dependencyContainer.spec.ts b/packages/framework/synthesize/src/test/dependencyContainer.spec.ts index 4721c1e60d24..b453dc445623 100644 --- a/packages/framework/synthesize/src/test/dependencyContainer.spec.ts +++ b/packages/framework/synthesize/src/test/dependencyContainer.spec.ts @@ -10,10 +10,8 @@ import { IFluidHandleContext, IFluidHandle, IProvideFluidLoadable, - IProvideFluidRouter, IProvideFluidHandle, FluidObject, - IFluidRouter, } from "@fluidframework/core-interfaces"; import { FluidObjectHandle } from "@fluidframework/datastore"; @@ -44,20 +42,21 @@ class MockLoadable implements IFluidLoadable { } } -class MockFluidRouter implements IFluidRouter { - public get IFluidRouter() { +const ISomeObject: keyof IProvideSomeObject = "ISomeObject"; +interface IProvideSomeObject { + readonly ISomeObject: ISomeObject; +} +interface ISomeObject extends IProvideSomeObject { + value: number; +} +class MockSomeObject implements ISomeObject { + public get ISomeObject() { return this; } - public async request() { - return { - mimeType: "", - status: 200, - value: "", - }; - } + public readonly value = 0; } -describe("Routerlicious", () => { +describe("someObjectlicious", () => { describe("Aqueduct", () => { describe("DependencyContainer", () => { it(`One Optional Provider registered via value`, async () => { @@ -245,73 +244,73 @@ describe("Routerlicious", () => { }); it(`Two Optional Modules all registered`, async () => { - const dc = new DependencyContainer>(); + const dc = new DependencyContainer>(); const loadableMock = new MockLoadable(); dc.register(IFluidLoadable, loadableMock); - const routerMock = new MockFluidRouter(); - dc.register(IFluidRouter, routerMock); + const someObjectMock = new MockSomeObject(); + dc.register(ISomeObject, someObjectMock); - const s = dc.synthesize( - { IFluidLoadable, IFluidRouter }, + const s = dc.synthesize( + { IFluidLoadable, ISomeObject }, undefined, ); const loadable = await s.IFluidLoadable; assert(loadable, "Optional IFluidLoadable was registered"); assert(loadable === loadableMock, "IFluidLoadable is expected"); - const router = await s.IFluidRouter; - assert(router, "Optional IFluidRouter was registered"); - assert(router === routerMock, "IFluidRouter is expected"); + const someObject = await s.ISomeObject; + assert(someObject, "Optional ISomeObject was registered"); + assert(someObject === someObjectMock, "ISomeObject is expected"); }); it(`Two Optional Modules one registered`, async () => { - const dc = new DependencyContainer>(); + const dc = new DependencyContainer>(); const loadableMock = new MockLoadable(); dc.register(IFluidLoadable, loadableMock); - const s = dc.synthesize( - { IFluidLoadable, IFluidRouter }, + const s = dc.synthesize( + { IFluidLoadable, ISomeObject }, undefined, ); const loadable = await s.IFluidLoadable; assert(loadable, "Optional IFluidLoadable was registered"); assert(loadable === loadableMock, "IFluidLoadable is expected"); - const router = await s.IFluidRouter; - assert(!router, "Optional IFluidRouter was not registered"); + const someObject = await s.ISomeObject; + assert(!someObject, "Optional ISomeObject was not registered"); }); it(`Two Optional Modules none registered`, async () => { - const dc = new DependencyContainer>(); + const dc = new DependencyContainer>(); - const s = dc.synthesize( - { IFluidLoadable, IFluidRouter }, + const s = dc.synthesize( + { IFluidLoadable, ISomeObject }, undefined, ); const loadable = await s.IFluidLoadable; assert(!loadable, "Optional IFluidLoadable was not registered"); - const router = await s.IFluidRouter; - assert(!router, "Optional IFluidRouter was not registered"); + const someObject = await s.ISomeObject; + assert(!someObject, "Optional ISomeObject was not registered"); }); it(`Two Required Modules all registered`, async () => { - const dc = new DependencyContainer>(); + const dc = new DependencyContainer>(); const loadableMock = new MockLoadable(); dc.register(IFluidLoadable, loadableMock); - const routerMock = new MockFluidRouter(); - dc.register(IFluidRouter, routerMock); + const someObjectMock = new MockSomeObject(); + dc.register(ISomeObject, someObjectMock); - const s = dc.synthesize( + const s = dc.synthesize( undefined, - { IFluidLoadable, IFluidRouter }, + { IFluidLoadable, ISomeObject }, ); const loadable = await s.IFluidLoadable; assert(loadable, "Required IFluidLoadable was registered"); assert(loadable === loadableMock, "IFluidLoadable is expected"); - const router = await s.IFluidRouter; - assert(router, "Required IFluidRouter was registered"); - assert(router === routerMock, "IFluidRouter is expected"); + const someObject = await s.ISomeObject; + assert(someObject, "Required ISomeObject was registered"); + assert(someObject === someObjectMock, "ISomeObject is expected"); }); it(`Required Provider not registered should throw`, async () => { @@ -346,21 +345,21 @@ describe("Routerlicious", () => { const parentDc = new DependencyContainer>(); const loadableMock = new MockLoadable(); parentDc.register(IFluidLoadable, loadableMock); - const dc = new DependencyContainer>(parentDc); - const routerMock = new MockFluidRouter(); - dc.register(IFluidRouter, routerMock); + const dc = new DependencyContainer>(parentDc); + const someObjectMock = new MockSomeObject(); + dc.register(ISomeObject, someObjectMock); - const s = dc.synthesize( - { IFluidLoadable, IFluidRouter }, + const s = dc.synthesize( + { IFluidLoadable, ISomeObject }, undefined, ); const loadable = await s.IFluidLoadable; assert(loadable, "Optional IFluidLoadable was registered"); assert(loadable === loadableMock, "IFluidLoadable is expected"); - const router = await s.IFluidRouter; - assert(router, "Optional IFluidRouter was registered"); - assert(router === routerMock, "IFluidRouter is expected"); + const someObject = await s.ISomeObject; + assert(someObject, "Optional ISomeObject was registered"); + assert(someObject === someObjectMock, "ISomeObject is expected"); }); it(`Optional Provider found in Parent and Child resolves Child`, async () => { @@ -398,21 +397,21 @@ describe("Routerlicious", () => { const parentDc = new DependencyContainer>(); const loadableMock = new MockLoadable(); parentDc.register(IFluidLoadable, loadableMock); - const dc = new DependencyContainer>(parentDc); - const routerMock = new MockFluidRouter(); - dc.register(IFluidRouter, routerMock); + const dc = new DependencyContainer>(parentDc); + const someObjectMock = new MockSomeObject(); + dc.register(ISomeObject, someObjectMock); - const s = dc.synthesize( + const s = dc.synthesize( undefined, - { IFluidLoadable, IFluidRouter }, + { IFluidLoadable, ISomeObject }, ); const loadable = await s.IFluidLoadable; assert(loadable, "Required IFluidLoadable was registered"); assert(loadable === loadableMock, "IFluidLoadable is expected"); - const router = await s.IFluidRouter; - assert(router, "Required IFluidRouter was registered"); - assert(router === routerMock, "IFluidRouter is expected"); + const someObject = await s.ISomeObject; + assert(someObject, "Required ISomeObject was registered"); + assert(someObject === someObjectMock, "ISomeObject is expected"); }); it(`Required Provider found in Parent and Child resolves Child`, async () => { @@ -458,14 +457,14 @@ describe("Routerlicious", () => { }); it(`has() resolves correctly in all variations`, async () => { - const dc = new DependencyContainer>(); + const dc = new DependencyContainer>(); dc.register(IFluidLoadable, new MockLoadable()); - dc.register(IFluidRouter, new MockFluidRouter()); + dc.register(ISomeObject, new MockSomeObject()); assert(dc.has(IFluidLoadable), "Manager has IFluidLoadable"); - assert(dc.has(IFluidRouter), "Manager has IFluidRouter"); + assert(dc.has(ISomeObject), "Manager has ISomeObject"); assert( - dc.has(IFluidLoadable) && dc.has(IFluidRouter), - "Manager has IFluidLoadable & IFluidRouter", + dc.has(IFluidLoadable) && dc.has(ISomeObject), + "Manager has IFluidLoadable & ISomeObject", ); }); @@ -473,16 +472,16 @@ describe("Routerlicious", () => { const parentDc = new DependencyContainer>(); const loadableMock = new MockLoadable(); parentDc.register(IFluidLoadable, loadableMock); - const dc = new DependencyContainer>(parentDc); - const routerMock = new MockFluidRouter(); - dc.register(IFluidRouter, routerMock); + const dc = new DependencyContainer>(parentDc); + const someObjectMock = new MockSomeObject(); + dc.register(ISomeObject, someObjectMock); assert(dc.has(IFluidLoadable), "has includes parent registered"); assert( !dc.has(IFluidLoadable, true), "has does not include excluded parent registered", ); - assert(dc.has(IFluidRouter), "has includes registered"); + assert(dc.has(ISomeObject), "has includes registered"); assert(!dc.has(IFluidHandle), "does not include not registered"); }); diff --git a/packages/framework/tinylicious-client/api-report/tinylicious-client.api.md b/packages/framework/tinylicious-client/api-report/tinylicious-client.api.md index f80d44f1485d..6454e5da75a5 100644 --- a/packages/framework/tinylicious-client/api-report/tinylicious-client.api.md +++ b/packages/framework/tinylicious-client/api-report/tinylicious-client.api.md @@ -5,7 +5,6 @@ ```ts import { ContainerSchema } from '@fluidframework/fluid-static'; -import { IClient } from '@fluidframework/protocol-definitions'; import { IFluidContainer } from '@fluidframework/fluid-static'; import { IMember } from '@fluidframework/fluid-static'; import { IServiceAudience } from '@fluidframework/fluid-static'; @@ -13,7 +12,6 @@ import { ITelemetryBaseEvent } from '@fluidframework/core-interfaces'; import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; import { ITokenProvider } from '@fluidframework/routerlicious-driver'; import { IUser } from '@fluidframework/protocol-definitions'; -import { ServiceAudience } from '@fluidframework/fluid-static'; export { ITelemetryBaseEvent } @@ -22,12 +20,6 @@ export { ITelemetryBaseLogger } // @internal export type ITinyliciousAudience = IServiceAudience; -// @internal @deprecated -export class TinyliciousAudience extends ServiceAudience implements ITinyliciousAudience { - // (undocumented) - protected createServiceMember(audienceMember: IClient): TinyliciousMember; -} - // @internal class TinyliciousClient { constructor(props?: TinyliciousClientProps | undefined); diff --git a/packages/framework/tinylicious-client/package.json b/packages/framework/tinylicious-client/package.json index 263b3db68461..aab78f98dc29 100644 --- a/packages/framework/tinylicious-client/package.json +++ b/packages/framework/tinylicious-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/tinylicious-client", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A tool to enable creation and loading of Fluid containers using the Tinylicious service", "homepage": "https://fluidframework.com", "repository": { @@ -108,6 +108,11 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedClassDeclaration_TinyliciousAudience": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/framework/tinylicious-client/src/TinyliciousAudience.ts b/packages/framework/tinylicious-client/src/TinyliciousAudience.ts index 7ca50e4acf9c..fe92f445fac3 100644 --- a/packages/framework/tinylicious-client/src/TinyliciousAudience.ts +++ b/packages/framework/tinylicious-client/src/TinyliciousAudience.ts @@ -4,24 +4,8 @@ */ import { assert } from "@fluidframework/core-utils"; -import { ServiceAudience } from "@fluidframework/fluid-static"; import { IClient } from "@fluidframework/protocol-definitions"; -import { ITinyliciousAudience, TinyliciousMember, TinyliciousUser } from "./interfaces"; - -/** - * {@inheritDoc ITinyliciousAudience} - * @deprecated use {@link ITinyliciousAudience} instead - * @internal - */ -export class TinyliciousAudience - extends ServiceAudience - implements ITinyliciousAudience -{ - /***/ - protected createServiceMember(audienceMember: IClient): TinyliciousMember { - return createTinyliciousAudienceMember(audienceMember); - } -} +import { TinyliciousMember, TinyliciousUser } from "./interfaces"; export function createTinyliciousAudienceMember(audienceMember: IClient): TinyliciousMember { const tinyliciousUser = audienceMember.user as TinyliciousUser; diff --git a/packages/framework/tinylicious-client/src/TinyliciousClient.ts b/packages/framework/tinylicious-client/src/TinyliciousClient.ts index 8ab83b5bf56a..6388617b01b8 100644 --- a/packages/framework/tinylicious-client/src/TinyliciousClient.ts +++ b/packages/framework/tinylicious-client/src/TinyliciousClient.ts @@ -170,13 +170,11 @@ export class TinyliciousClient { } private async getContainerEntryPoint(container: IContainer): Promise { - const rootDataObject: FluidObject | undefined = - await container.getEntryPoint(); - assert(rootDataObject !== undefined, "entryPoint must exist"); - // ! This "if" is needed for back-compat (older instances of IRootDataObject may not have the IRootDataObject property) - if (rootDataObject.IRootDataObject === undefined) { - return rootDataObject as IRootDataObject; - } + const rootDataObject: FluidObject = await container.getEntryPoint(); + assert( + rootDataObject.IRootDataObject !== undefined, + "entryPoint must be of type IRootDataObject", + ); return rootDataObject.IRootDataObject; } // #endregion diff --git a/packages/framework/tinylicious-client/src/index.ts b/packages/framework/tinylicious-client/src/index.ts index ec23a9b59ea5..03dbb5bcbf19 100644 --- a/packages/framework/tinylicious-client/src/index.ts +++ b/packages/framework/tinylicious-client/src/index.ts @@ -26,7 +26,6 @@ export { TinyliciousMember, TinyliciousUser, } from "./interfaces"; -export { TinyliciousAudience } from "./TinyliciousAudience"; export { TinyliciousClient } from "./TinyliciousClient"; // eslint-disable-next-line import/no-default-export export default TinyliciousClient; diff --git a/packages/framework/tinylicious-client/src/test/types/validateTinyliciousClientPrevious.generated.ts b/packages/framework/tinylicious-client/src/test/types/validateTinyliciousClientPrevious.generated.ts index e54d4baebf44..e50a11c8bb3b 100644 --- a/packages/framework/tinylicious-client/src/test/types/validateTinyliciousClientPrevious.generated.ts +++ b/packages/framework/tinylicious-client/src/test/types/validateTinyliciousClientPrevious.generated.ts @@ -96,26 +96,14 @@ use_old_TypeAliasDeclaration_ITinyliciousAudience( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_TinyliciousAudience": {"forwardCompat": false} +* "RemovedClassDeclaration_TinyliciousAudience": {"forwardCompat": false} */ -declare function get_old_ClassDeclaration_TinyliciousAudience(): - TypeOnly; -declare function use_current_ClassDeclaration_TinyliciousAudience( - use: TypeOnly): void; -use_current_ClassDeclaration_TinyliciousAudience( - get_old_ClassDeclaration_TinyliciousAudience()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "ClassDeclaration_TinyliciousAudience": {"backCompat": false} +* "RemovedClassDeclaration_TinyliciousAudience": {"backCompat": false} */ -declare function get_current_ClassDeclaration_TinyliciousAudience(): - TypeOnly; -declare function use_old_ClassDeclaration_TinyliciousAudience( - use: TypeOnly): void; -use_old_ClassDeclaration_TinyliciousAudience( - get_current_ClassDeclaration_TinyliciousAudience()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index 855f5ebdd9f1..b926ed228e4f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Undo Redo", "homepage": "https://fluidframework.com", "repository": { @@ -111,6 +111,11 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_SharedSegmentSequenceRevertible": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts b/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts index d2a3956eb1fa..54b8b60f948c 100644 --- a/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts +++ b/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts @@ -103,6 +103,7 @@ declare function get_old_ClassDeclaration_SharedSegmentSequenceRevertible(): declare function use_current_ClassDeclaration_SharedSegmentSequenceRevertible( use: TypeOnly): void; use_current_ClassDeclaration_SharedSegmentSequenceRevertible( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSegmentSequenceRevertible()); /* @@ -115,6 +116,7 @@ declare function get_current_ClassDeclaration_SharedSegmentSequenceRevertible(): declare function use_old_ClassDeclaration_SharedSegmentSequenceRevertible( use: TypeOnly): void; use_old_ClassDeclaration_SharedSegmentSequenceRevertible( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSegmentSequenceRevertible()); /* diff --git a/packages/framework/view-adapters/package.json b/packages/framework/view-adapters/package.json index 9d7d75ac7a6e..88ec5f0cafca 100644 --- a/packages/framework/view-adapters/package.json +++ b/packages/framework/view-adapters/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/view-adapters", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "View adapters for rendering views of alternate or unknown UI frameworks", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/view-interfaces/package.json b/packages/framework/view-interfaces/package.json index c449eeffdd9f..840fcd9c6fe2 100644 --- a/packages/framework/view-interfaces/package.json +++ b/packages/framework/view-interfaces/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/view-interfaces", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "View interfaces for rendering views", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/loader/container-loader/api-report/container-loader.api.md b/packages/loader/container-loader/api-report/container-loader.api.md index dcf1651304f2..e6ffd000d681 100644 --- a/packages/loader/container-loader/api-report/container-loader.api.md +++ b/packages/loader/container-loader/api-report/container-loader.api.md @@ -14,7 +14,6 @@ import { IDocumentServiceFactory } from '@fluidframework/driver-definitions'; import { IDocumentStorageService } from '@fluidframework/driver-definitions'; import { IFluidCodeDetails } from '@fluidframework/container-definitions'; import { IFluidModule } from '@fluidframework/container-definitions'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IHostLoader } from '@fluidframework/container-definitions'; import { ILoaderOptions as ILoaderOptions_2 } from '@fluidframework/container-definitions'; import { ILocationRedirectionError } from '@fluidframework/driver-definitions'; @@ -22,8 +21,6 @@ import { IProtocolHandler as IProtocolHandler_2 } from '@fluidframework/protocol import { IProvideFluidCodeDetailsComparer } from '@fluidframework/container-definitions'; import { IQuorumSnapshot } from '@fluidframework/protocol-base'; import { IRequest } from '@fluidframework/core-interfaces'; -import { IRequestHeader } from '@fluidframework/core-interfaces'; -import { IResponse } from '@fluidframework/core-interfaces'; import { ISignalMessage } from '@fluidframework/protocol-definitions'; import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; @@ -118,15 +115,11 @@ export class Loader implements IHostLoader { canReconnect?: boolean; clientDetailsOverride?: IClientDetails; }): Promise; - // @deprecated (undocumented) - get IFluidRouter(): IFluidRouter; // (undocumented) rehydrateDetachedContainerFromSnapshot(snapshot: string, createDetachedProps?: { canReconnect?: boolean; clientDetailsOverride?: IClientDetails; }): Promise; - // @deprecated (undocumented) - request(request: IRequest): Promise; // (undocumented) resolve(request: IRequest, pendingLocalState?: string): Promise; // (undocumented) @@ -136,9 +129,6 @@ export class Loader implements IHostLoader { // @alpha export type ProtocolHandlerBuilder = (attributes: IDocumentAttributes, snapshot: IQuorumSnapshot, sendProposal: (key: string, value: any) => number) => IProtocolHandler; -// @internal @deprecated -export function requestResolvedObjectFromContainer(container: IContainer, headers?: IRequestHeader): Promise; - // @internal export function resolveWithLocationRedirectionHandling(api: (request: IRequest) => Promise, request: IRequest, urlResolver: IUrlResolver, logger?: ITelemetryBaseLogger): Promise; diff --git a/packages/loader/container-loader/package.json b/packages/loader/container-loader/package.json index de40e42ab1c6..f3d50f1bd124 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container loader", "homepage": "https://fluidframework.com", "repository": { @@ -124,6 +124,17 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedFunctionDeclaration_requestResolvedObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_IContainerExperimental": { + "backCompat": false + }, + "ClassDeclaration_Loader": { + "backCompat": false + } + } } } diff --git a/packages/loader/container-loader/src/container.ts b/packages/loader/container-loader/src/container.ts index 35e2baa4f4c6..89ee62c8228f 100644 --- a/packages/loader/container-loader/src/container.ts +++ b/packages/loader/container-loader/src/container.ts @@ -11,9 +11,6 @@ import { ITelemetryProperties, TelemetryEventCategory, IRequest, - IResponse, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, FluidObject, LogLevel, } from "@fluidframework/core-interfaces"; @@ -601,14 +598,6 @@ export class Container return this._deltaManager.connectionManager.connectionMode; } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - public get IFluidRouter(): IFluidRouter { - return this; - } - public get resolvedUrl(): IResolvedUrl | undefined { /** * All attached containers will have a document service, @@ -718,14 +707,14 @@ export class Container /** * {@inheritDoc @fluidframework/container-definitions#IContainer.entryPoint} */ - public async getEntryPoint(): Promise { + public async getEntryPoint(): Promise { if (this._disposed) { throw new UsageError("The context is already disposed"); } if (this._runtime !== undefined) { return this._runtime.getEntryPoint?.(); } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const runtimeInstantiatedHandler = () => { assert( this._runtime !== undefined, @@ -1355,18 +1344,6 @@ export class Container ); } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public async request(path: IRequest): Promise { - return PerformanceEvent.timedExecAsync( - this.mc.logger, - { eventName: "Request" }, - async () => this.runtime.request(path), - { end: true, cancel: "error" }, - ); - } - private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) { const currentMode = this._deltaManager.connectionManager.reconnectMode; diff --git a/packages/loader/container-loader/src/containerStorageAdapter.ts b/packages/loader/container-loader/src/containerStorageAdapter.ts index 1e2b39b6da36..ec8a5d918977 100644 --- a/packages/loader/container-loader/src/containerStorageAdapter.ts +++ b/packages/loader/container-loader/src/containerStorageAdapter.ts @@ -106,8 +106,10 @@ export class ContainerStorageAdapter implements IDocumentStorageService, IDispos } private getBlobContents(snapshotTree: ISnapshotTreeWithBlobContents) { - for (const [id, value] of Object.entries(snapshotTree.blobsContents ?? {})) { - this.blobContents[id] = value; + if (snapshotTree.blobsContents !== undefined) { + for (const [id, value] of Object.entries(snapshotTree.blobsContents ?? {})) { + this.blobContents[id] = value; + } } for (const [_, tree] of Object.entries(snapshotTree.trees)) { this.getBlobContents(tree); diff --git a/packages/loader/container-loader/src/index.ts b/packages/loader/container-loader/src/index.ts index 8d1d207ac89f..43dd414842a5 100644 --- a/packages/loader/container-loader/src/index.ts +++ b/packages/loader/container-loader/src/index.ts @@ -13,7 +13,6 @@ export { ILoaderProps, ILoaderServices, Loader, - requestResolvedObjectFromContainer, } from "./loader"; export { isLocationRedirectionError, diff --git a/packages/loader/container-loader/src/loader.ts b/packages/loader/container-loader/src/loader.ts index c042e8dd93db..85fc49214eb3 100644 --- a/packages/loader/container-loader/src/loader.ts +++ b/packages/loader/container-loader/src/loader.ts @@ -16,11 +16,7 @@ import { import { ITelemetryBaseLogger, FluidObject, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IRequest, - IRequestHeader, - IResponse, IConfigProviderBase, } from "@fluidframework/core-interfaces"; import { @@ -62,14 +58,6 @@ export class RelativeLoader implements ILoader { private readonly loader: ILoader | undefined, ) {} - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request. - */ - // eslint-disable-next-line import/no-deprecated - public get IFluidRouter(): IFluidRouter { - return this; - } - public async resolve(request: IRequest): Promise { if (request.url.startsWith("/")) { ensureResolvedUrlDefined(this.container.resolvedUrl); @@ -92,25 +80,6 @@ export class RelativeLoader implements ILoader { } return this.loader.resolve(request); } - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public async request(request: IRequest): Promise { - if (request.url.startsWith("/")) { - const container = await this.resolve(request); - return container.request(request); - } - - if (this.loader === undefined) { - return { - status: 404, - value: "Cannot request external containers", - mimeType: "plain/text", - }; - } - return this.loader.request(request); - } } /** @@ -277,34 +246,6 @@ export type IDetachedBlobStorage = Pick { - ensureResolvedUrlDefined(container.resolvedUrl); - const parsedUrl = tryParseCompatibleResolvedUrl(container.resolvedUrl.url); - - if (parsedUrl === undefined) { - throw new Error(`Invalid URL ${container.resolvedUrl.url}`); - } - - // eslint-disable-next-line import/no-deprecated - const entryPoint: FluidObject | undefined = await container.getEntryPoint?.(); - const router = entryPoint?.IFluidRouter ?? container.IFluidRouter; - - return router.request({ - url: `${parsedUrl.path}${parsedUrl.query}`, - headers, - }); -} - /** * Manages Fluid resource loading * @alpha @@ -356,14 +297,6 @@ export class Loader implements IHostLoader { }); } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request. - */ - // eslint-disable-next-line import/no-deprecated - public get IFluidRouter(): IFluidRouter { - return this; - } - public async createDetachedContainer( codeDetails: IFluidCodeDetails, createDetachedProps?: { @@ -407,23 +340,6 @@ export class Loader implements IHostLoader { }); } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request. - */ - public async request(request: IRequest): Promise { - return PerformanceEvent.timedExecAsync( - this.mc.logger, - { eventName: "Request" }, - async () => { - const resolved = await this.resolveCore(request); - return resolved.container.request({ - ...request, - url: `${resolved.parsed.path}${resolved.parsed.query}`, - }); - }, - ); - } - private async resolveCore( request: IRequest, pendingLocalState?: IPendingContainerState, diff --git a/packages/loader/container-loader/src/packageVersion.ts b/packages/loader/container-loader/src/packageVersion.ts index 38e8ee59a062..d9a4b807dc70 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/loader/container-loader/src/test/container.spec.ts b/packages/loader/container-loader/src/test/container.spec.ts index 70923413fd87..1ec35cfc86fc 100644 --- a/packages/loader/container-loader/src/test/container.spec.ts +++ b/packages/loader/container-loader/src/test/container.spec.ts @@ -14,7 +14,6 @@ import { ReadOnlyInfo, } from "@fluidframework/container-definitions"; import { TypedEventEmitter } from "@fluid-internal/client-utils"; -import { IFluidRouter } from "@fluidframework/core-interfaces"; import { IResolvedUrl } from "@fluidframework/driver-definitions"; import { ISequencedDocumentMessage, IDocumentMessage } from "@fluidframework/protocol-definitions"; import { waitContainerToCatchUp } from "../container"; @@ -47,7 +46,6 @@ class MockContainer audience?: IAudience | undefined; clientId?: string | undefined; readOnlyInfo?: ReadOnlyInfo | undefined; - IFluidRouter?: IFluidRouter | undefined; get mockDeltaManager() { return this.deltaManager as any as MockDeltaManager; diff --git a/packages/loader/container-loader/src/test/snapshotConversionTest.spec.ts b/packages/loader/container-loader/src/test/snapshotConversionTest.spec.ts index eebf08e8e4f5..030ee6f0be2e 100644 --- a/packages/loader/container-loader/src/test/snapshotConversionTest.spec.ts +++ b/packages/loader/container-loader/src/test/snapshotConversionTest.spec.ts @@ -65,26 +65,22 @@ describe("Dehydrate Container", () => { // Validate the ".component" blob. const defaultDataStoreBlobId = snapshotTree.trees.default.blobs[".component"]; + const defaultDataStoreBlob = + snapshotTree.trees.default.blobsContents?.[defaultDataStoreBlobId]; + assert.strict(defaultDataStoreBlob, "defaultDataStoreBlob undefined"); assert.strictEqual( - JSON.parse( - bufferToString( - snapshotTree.trees.default.blobsContents[defaultDataStoreBlobId], - "utf8", - ), - ), + JSON.parse(bufferToString(defaultDataStoreBlob, "utf8")), "defaultDataStore", "The .component blob's content is incorrect", ); // Validate "root" sub-tree. const rootAttributesBlobId = snapshotTree.trees.default.trees.root.blobs.attributes; + const rootAttributesBlob = + snapshotTree.trees.default.trees.root.blobsContents?.[rootAttributesBlobId]; + assert.strict(rootAttributesBlob, "rootAttributesBlob undefined"); assert.strictEqual( - JSON.parse( - bufferToString( - snapshotTree.trees.default.trees.root.blobsContents[rootAttributesBlobId], - "utf8", - ), - ), + JSON.parse(bufferToString(rootAttributesBlob, "utf8")), "rootattributes", "The root sub-tree's content is incorrect", ); diff --git a/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts b/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts index 4167455496cc..1b4278c451c2 100644 --- a/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts +++ b/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts @@ -91,6 +91,7 @@ declare function get_current_InterfaceDeclaration_IContainerExperimental(): declare function use_old_InterfaceDeclaration_IContainerExperimental( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainerExperimental( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainerExperimental()); /* @@ -283,6 +284,7 @@ declare function get_current_ClassDeclaration_Loader(): declare function use_old_ClassDeclaration_Loader( use: TypeOnly): void; use_old_ClassDeclaration_Loader( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_Loader()); /* @@ -336,26 +338,14 @@ use_old_FunctionDeclaration_isLocationRedirectionError( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_requestResolvedObjectFromContainer": {"forwardCompat": false} +* "RemovedFunctionDeclaration_requestResolvedObjectFromContainer": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_requestResolvedObjectFromContainer(): - TypeOnly; -declare function use_current_FunctionDeclaration_requestResolvedObjectFromContainer( - use: TypeOnly): void; -use_current_FunctionDeclaration_requestResolvedObjectFromContainer( - get_old_FunctionDeclaration_requestResolvedObjectFromContainer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_requestResolvedObjectFromContainer": {"backCompat": false} +* "RemovedFunctionDeclaration_requestResolvedObjectFromContainer": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_requestResolvedObjectFromContainer(): - TypeOnly; -declare function use_old_FunctionDeclaration_requestResolvedObjectFromContainer( - use: TypeOnly): void; -use_old_FunctionDeclaration_requestResolvedObjectFromContainer( - get_current_FunctionDeclaration_requestResolvedObjectFromContainer()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/loader/driver-utils/package.json b/packages/loader/driver-utils/package.json index 5dd44c61c2a8..81748b5a4ae5 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Collection of utility functions for Fluid drivers", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/loader/driver-utils/src/packageVersion.ts b/packages/loader/driver-utils/src/packageVersion.ts index 3f702fe99f24..202ca1968124 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/loader/location-redirection-utils/.eslintrc.js b/packages/loader/location-redirection-utils/.eslintrc.js deleted file mode 100644 index fb70cea65365..000000000000 --- a/packages/loader/location-redirection-utils/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - extends: ["@fluidframework/eslint-config-fluid/minimal", "prettier"], - parserOptions: { - project: ["./tsconfig.json", "./src/test/tsconfig.json"], - }, - rules: { - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/strict-boolean-expressions": "off", - "no-case-declarations": "off", - }, -}; diff --git a/packages/loader/location-redirection-utils/.gitignore b/packages/loader/location-redirection-utils/.gitignore deleted file mode 100644 index 90d45b85ab68..000000000000 --- a/packages/loader/location-redirection-utils/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -lib \ No newline at end of file diff --git a/packages/loader/location-redirection-utils/.mocharc.js b/packages/loader/location-redirection-utils/.mocharc.js deleted file mode 100644 index 81dd8e6fb139..000000000000 --- a/packages/loader/location-redirection-utils/.mocharc.js +++ /dev/null @@ -1,12 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -"use strict"; - -const getFluidTestMochaConfig = require("@fluidframework/mocha-test-setup/mocharc-common"); - -const packageDir = __dirname; -const config = getFluidTestMochaConfig(packageDir); -module.exports = config; diff --git a/packages/loader/location-redirection-utils/.npmignore b/packages/loader/location-redirection-utils/.npmignore deleted file mode 100644 index a40f882cf599..000000000000 --- a/packages/loader/location-redirection-utils/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -nyc -*.log -**/*.tsbuildinfo -src/test -dist/test -**/_api-extractor-temp/** diff --git a/packages/loader/location-redirection-utils/CHANGELOG.md b/packages/loader/location-redirection-utils/CHANGELOG.md deleted file mode 100644 index e4859ff13b9f..000000000000 --- a/packages/loader/location-redirection-utils/CHANGELOG.md +++ /dev/null @@ -1,79 +0,0 @@ -# @fluidframework/location-redirection-utils - -## 2.0.0-internal.7.3.0 - -Dependency updates only. - -## 2.0.0-internal.7.2.0 - -Dependency updates only. - -## 2.0.0-internal.7.1.0 - -### Minor Changes - -- Move `location-redirection-utils` APIs to `container-loader` ([#17554](https://github.com/microsoft/FluidFramework/issues/17554)) [17acf10a71](https://github.com/microsoft/FluidFramework/commits/17acf10a71e51e2490d1df57c89430c1be04c345) - - Moves the 2 package exports of `location-redirection-utils` to the `container-loader` package. - - Exports from `location-redirection-utils` are now deprecated, and the package itself will be removed in an upcoming release. - -## 2.0.0-internal.7.0.0 - -### Major Changes - -- Minimum TypeScript version now 5.1.6 [871b3493dd](https://github.com/microsoft/FluidFramework/commits/871b3493dd0d7ea3a89be64998ceb6cb9021a04e) - - The minimum supported TypeScript version for Fluid 2.0 clients is now 5.1.6. - -## 2.0.0-internal.6.4.0 - -Dependency updates only. - -## 2.0.0-internal.6.3.0 - -Dependency updates only. - -## 2.0.0-internal.6.2.0 - -Dependency updates only. - -## 2.0.0-internal.6.1.0 - -Dependency updates only. - -## 2.0.0-internal.6.0.0 - -### Major Changes - -- Upgraded typescript transpilation target to ES2020 [8abce8cdb4](https://github.com/microsoft/FluidFramework/commits/8abce8cdb4e2832fb6405fb44e393bef03d5648a) - - Upgraded typescript transpilation target to ES2020. This is done in order to decrease the bundle sizes of Fluid Framework packages. This has provided size improvements across the board for ex. Loader, Driver, Runtime etc. Reduced bundle sizes helps to load lesser code in apps and hence also helps to improve the perf.If any app wants to target any older versions of browsers with which this target version is not compatible, then they can use packages like babel to transpile to a older target. - -## 2.0.0-internal.5.4.0 - -Dependency updates only. - -## 2.0.0-internal.5.3.0 - -Dependency updates only. - -## 2.0.0-internal.5.2.0 - -Dependency updates only. - -## 2.0.0-internal.5.1.0 - -Dependency updates only. - -## 2.0.0-internal.5.0.0 - -Dependency updates only. - -## 2.0.0-internal.4.4.0 - -Dependency updates only. - -## 2.0.0-internal.4.1.0 - -Dependency updates only. diff --git a/packages/loader/location-redirection-utils/LICENSE b/packages/loader/location-redirection-utils/LICENSE deleted file mode 100644 index 60af0a6a40e9..000000000000 --- a/packages/loader/location-redirection-utils/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) Microsoft Corporation and contributors. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/loader/location-redirection-utils/README.md b/packages/loader/location-redirection-utils/README.md deleted file mode 100644 index f1c934c21714..000000000000 --- a/packages/loader/location-redirection-utils/README.md +++ /dev/null @@ -1,84 +0,0 @@ -# @fluidframework/location-redirection-utils - -**NOTE: THIS PACKAGE IS DEPRECATED AND WILL BE REMOVED IN A FUTURE RELEASE.** -**ALL EXPORTS HAVE BEEN MARKED AS DEPRECATED, AND HAVE BEEN MOVED TO THE `@fluidframework/container-loader` PACKAGE.** -**PLEASE MIGRATE ANY IMPORTS FROM THIS PACKAGE AS NEEDED.** -**THIS PACKAGE WILL RECEIVE NO FURTHER SUPPORT.** - -Shared utilities for handling location change of container on server. - - - - - - -## Using Fluid Framework libraries - -When taking a dependency on a Fluid Framework library, we recommend using a `^` (caret) version range, such as `^1.3.4`. -While Fluid Framework libraries may use different ranges with interdependencies between other Fluid Framework libraries, -library consumers should always prefer `^`. - -Note that when depending on a library version of the form `2.0.0-internal.x.y.z`, called the Fluid internal version scheme, -you must use a `>= <` dependency range (such as `>=2.0.0-internal.x.y.z <2.0.0-internal.w.0.0` where `w` is `x+1`). -Standard `^` and `~` ranges will not work as expected. -See the [@fluid-tools/version-tools](https://github.com/microsoft/FluidFramework/blob/main/build-tools/packages/version-tools/README.md) -package for more information including tools to convert between version schemes. - - - - - -## Explanation of the scenario: - -This talks about loading of a container by handling location change of container in the storage. For example, today -the site domain can change for a particular container on server for spo. The host/app does not know about this change -and they request the container using the old domain name and it will cause the container load request to fail. -`resolveWithLocationRedirectionHandling` utility handles that scenario and use the new absolute url supplied by the -driver when driver sees that error, to make the request again and successfully load the container. - -## Usage - -The host/apps needs to use this utility when they load the container. The below example tells how to use the utility. -Lets say host has the code to call Loader.resolve(IRequest) or Loader.request(IRequest, pendingLocalState) in their code, now they want to wrap that within this utility if they want to handle this case. - -``` -const container = await resolveWithLocationRedirectionHandling( - async (req: IRequest) => { - // Any other code that you want to execute like extract something info the request/ logging etc. This - // req is the new request with which loader.resolve will be called. - loader.resolve(req), - }, - request, // original request to be resolved - urlResolver, // urlResolver, same as hosts passes to the loader - logger, // logger which will help with the telemetry -); - -OR - -const response = await resolveWithLocationRedirectionHandling( - async (req: IRequest) => { - // Any other code that you want to execute like extract something info the request/ logging etc - loader.request(req, pendingLocalState), - }, - request, // original request to be resolved - urlResolver, // urlResolver, same as hosts passes to the loader - logger, // logger which will help with the telemetry -); -``` - - - - - - -## Trademark - -This project may contain Microsoft trademarks or logos for Microsoft projects, products, or services. - -Use of these trademarks or logos must follow Microsoft's [Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). - -Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. - - - - diff --git a/packages/loader/location-redirection-utils/api-report/location-redirection-utils.api.md b/packages/loader/location-redirection-utils/api-report/location-redirection-utils.api.md deleted file mode 100644 index ac9f2c1f2d24..000000000000 --- a/packages/loader/location-redirection-utils/api-report/location-redirection-utils.api.md +++ /dev/null @@ -1,14 +0,0 @@ -## API Report File for "@fluidframework/location-redirection-utils" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { isLocationRedirectionError } from '@fluidframework/container-loader'; -import { resolveWithLocationRedirectionHandling } from '@fluidframework/container-loader'; - -export { isLocationRedirectionError } - -export { resolveWithLocationRedirectionHandling } - -``` diff --git a/packages/loader/location-redirection-utils/package.json b/packages/loader/location-redirection-utils/package.json deleted file mode 100644 index 6e2c3250b934..000000000000 --- a/packages/loader/location-redirection-utils/package.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "name": "@fluidframework/location-redirection-utils", - "version": "2.0.0-internal.7.4.0", - "description": "Location Redirection Handling Utilities", - "homepage": "https://fluidframework.com", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/FluidFramework.git", - "directory": "packages/loader/location-redirection-utils" - }, - "license": "MIT", - "author": "Microsoft and contributors", - "sideEffects": false, - "main": "dist/index.js", - "module": "lib/index.js", - "types": "dist/index.d.ts", - "scripts": { - "api": "fluid-build . --task api", - "api-extractor:commonjs": "api-extractor run --local", - "api-extractor:esnext": "copyfiles -u 1 \"dist/**/*-@(alpha|beta|public|untrimmed).d.ts\" lib", - "build": "fluid-build . --task build", - "build:commonjs": "fluid-build . --task commonjs", - "build:compile": "fluid-build . --task compile", - "build:docs": "fluid-build . --task api", - "build:esnext": "tsc --project ./tsconfig.esnext.json", - "build:test": "tsc --project ./src/test/tsconfig.json", - "check:are-the-types-wrong": "attw --pack", - "check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json", - "ci:build:docs": "api-extractor run", - "clean": "rimraf --glob dist lib \"**/*.tsbuildinfo\" \"**/*.build.log\" _api-extractor-temp nyc", - "eslint": "eslint --format stylish src", - "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout", - "format": "npm run prettier:fix", - "lint": "npm run prettier && npm run check:release-tags && npm run eslint", - "lint:fix": "npm run prettier:fix && npm run eslint:fix", - "prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore", - "prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore", - "test": "echo \"Tests have been removed. This package is scheduled for deletion.\" && exit 1", - "tsc": "tsc", - "typetests:gen": "fluid-type-test-generator", - "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" - }, - "c8": { - "all": true, - "cache-dir": "nyc/.cache", - "exclude": [ - "src/test/**/*.ts", - "dist/test/**/*.js" - ], - "exclude-after-remap": false, - "include": [ - "src/**/*.ts", - "dist/**/*.js" - ], - "report-dir": "nyc/report", - "reporter": [ - "cobertura", - "html", - "text" - ], - "temp-directory": "nyc/.nyc_output" - }, - "dependencies": { - "@fluidframework/container-loader": "workspace:~", - "@fluidframework/core-interfaces": "workspace:~", - "@fluidframework/driver-definitions": "workspace:~", - "@fluidframework/telemetry-utils": "workspace:~" - }, - "devDependencies": { - "@arethetypeswrong/cli": "^0.13.3", - "@fluid-tools/build-cli": "^0.28.0", - "@fluidframework/build-common": "^2.0.3", - "@fluidframework/build-tools": "^0.28.0", - "@fluidframework/eslint-config-fluid": "^3.1.0", - "@fluidframework/location-redirection-utils-previous": "npm:@fluidframework/location-redirection-utils@2.0.0-internal.7.2.0", - "@fluidframework/mocha-test-setup": "workspace:~", - "@fluidframework/test-runtime-utils": "workspace:~", - "@microsoft/api-extractor": "^7.38.3", - "@types/mocha": "^9.1.1", - "@types/node": "^18.19.0", - "c8": "^7.7.1", - "copyfiles": "^2.4.1", - "cross-env": "^7.0.3", - "eslint": "~8.50.0", - "mocha": "^10.2.0", - "mocha-json-output-reporter": "^2.0.1", - "mocha-multi-reporters": "^1.5.1", - "moment": "^2.21.0", - "prettier": "~3.0.3", - "rimraf": "^4.4.0", - "typescript": "~5.1.6" - }, - "fluidBuild": { - "tasks": { - "build:docs": { - "dependsOn": [ - "...", - "api-extractor:commonjs", - "api-extractor:esnext" - ], - "script": false - } - } - }, - "typeValidation": { - "broken": {} - } -} diff --git a/packages/loader/location-redirection-utils/prettier.config.cjs b/packages/loader/location-redirection-utils/prettier.config.cjs deleted file mode 100644 index d4870022599f..000000000000 --- a/packages/loader/location-redirection-utils/prettier.config.cjs +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -module.exports = { - ...require("@fluidframework/build-common/prettier.config.cjs"), -}; diff --git a/packages/loader/location-redirection-utils/src/index.ts b/packages/loader/location-redirection-utils/src/index.ts deleted file mode 100644 index 94c220964872..000000000000 --- a/packages/loader/location-redirection-utils/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * Note: this package is deprecated and will be removed in a future release. - * - * @deprecated API elements from this package have moved to {@link @fluidframework/container-loader#}. - * - * @packageDocumentation - */ - -export { - /** - * @deprecated Import from {@link @fluidframework/container-loader#} - */ - isLocationRedirectionError, - /** - * @deprecated Import from {@link @fluidframework/container-loader#} - */ - resolveWithLocationRedirectionHandling, -} from "@fluidframework/container-loader"; diff --git a/packages/loader/location-redirection-utils/src/test/tsconfig.json b/packages/loader/location-redirection-utils/src/test/tsconfig.json deleted file mode 100644 index b58e17f1917f..000000000000 --- a/packages/loader/location-redirection-utils/src/test/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "@fluidframework/build-common/ts-common-config.json", - "compilerOptions": { - "rootDir": "./", - "outDir": "../../dist/test", - "types": ["node", "mocha"], - "declaration": false, - "declarationMap": false, - }, - "include": ["./**/*"], - "references": [ - { - "path": "../..", - }, - ], -} diff --git a/packages/loader/location-redirection-utils/src/test/types/validateLocationRedirectionUtilsPrevious.generated.ts b/packages/loader/location-redirection-utils/src/test/types/validateLocationRedirectionUtilsPrevious.generated.ts deleted file mode 100644 index 650fde8bd159..000000000000 --- a/packages/loader/location-redirection-utils/src/test/types/validateLocationRedirectionUtilsPrevious.generated.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * 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 fluid-type-test-generator in @fluidframework/build-tools. - */ -import type * as old from "@fluidframework/location-redirection-utils-previous"; -import type * as current from "../../index"; - - -// See 'build-tools/src/type-test-generator/compatibility.ts' for more information. -type TypeOnly = T extends number - ? number - : T extends string - ? string - : T extends boolean | bigint | symbol - ? T - : { - [P in keyof T]: TypeOnly; - }; - -/* -* Validate forward compat by using old type in place of current type -* If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_isLocationRedirectionError": {"forwardCompat": false} -*/ -declare function get_old_FunctionDeclaration_isLocationRedirectionError(): - TypeOnly; -declare function use_current_FunctionDeclaration_isLocationRedirectionError( - use: TypeOnly): void; -use_current_FunctionDeclaration_isLocationRedirectionError( - get_old_FunctionDeclaration_isLocationRedirectionError()); - -/* -* Validate back compat by using current type in place of old type -* If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_isLocationRedirectionError": {"backCompat": false} -*/ -declare function get_current_FunctionDeclaration_isLocationRedirectionError(): - TypeOnly; -declare function use_old_FunctionDeclaration_isLocationRedirectionError( - use: TypeOnly): void; -use_old_FunctionDeclaration_isLocationRedirectionError( - get_current_FunctionDeclaration_isLocationRedirectionError()); - -/* -* Validate forward compat by using old type in place of current type -* If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_resolveWithLocationRedirectionHandling": {"forwardCompat": false} -*/ -declare function get_old_FunctionDeclaration_resolveWithLocationRedirectionHandling(): - TypeOnly; -declare function use_current_FunctionDeclaration_resolveWithLocationRedirectionHandling( - use: TypeOnly): void; -use_current_FunctionDeclaration_resolveWithLocationRedirectionHandling( - get_old_FunctionDeclaration_resolveWithLocationRedirectionHandling()); - -/* -* Validate back compat by using current type in place of old type -* If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_resolveWithLocationRedirectionHandling": {"backCompat": false} -*/ -declare function get_current_FunctionDeclaration_resolveWithLocationRedirectionHandling(): - TypeOnly; -declare function use_old_FunctionDeclaration_resolveWithLocationRedirectionHandling( - use: TypeOnly): void; -use_old_FunctionDeclaration_resolveWithLocationRedirectionHandling( - get_current_FunctionDeclaration_resolveWithLocationRedirectionHandling()); diff --git a/packages/loader/location-redirection-utils/tsconfig.esnext.json b/packages/loader/location-redirection-utils/tsconfig.esnext.json deleted file mode 100644 index 1ea62d46b20b..000000000000 --- a/packages/loader/location-redirection-utils/tsconfig.esnext.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "module": "esnext", - }, -} diff --git a/packages/loader/location-redirection-utils/tsconfig.json b/packages/loader/location-redirection-utils/tsconfig.json deleted file mode 100644 index 9f03e2b6e02c..000000000000 --- a/packages/loader/location-redirection-utils/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@fluidframework/build-common/ts-common-config.json", - "exclude": ["src/test/**/*"], - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist", - "composite": true, - }, - "include": ["src/**/*"], -} diff --git a/packages/loader/test-loader-utils/package.json b/packages/loader/test-loader-utils/package.json index 57ca1ceca6ac..9fce0c0c0c5f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Mocks and other test utilities for the Fluid Framework Loader", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/container-runtime-definitions/api-report/container-runtime-definitions.api.md b/packages/runtime/container-runtime-definitions/api-report/container-runtime-definitions.api.md index 6f76935d8d85..17485b270f48 100644 --- a/packages/runtime/container-runtime-definitions/api-report/container-runtime-definitions.api.md +++ b/packages/runtime/container-runtime-definitions/api-report/container-runtime-definitions.api.md @@ -17,7 +17,6 @@ import { IEventProvider } from '@fluidframework/core-interfaces'; import { IFluidDataStoreContextDetached } from '@fluidframework/runtime-definitions'; import { IFluidHandle } from '@fluidframework/core-interfaces'; import { IFluidHandleContext } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { ILoaderOptions } from '@fluidframework/container-definitions'; import { IProvideFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IRequest } from '@fluidframework/core-interfaces'; @@ -40,13 +39,9 @@ export interface IContainerRuntime extends IProvideFluidDataStoreRegistry, ICont readonly flushMode: FlushMode; getAbsoluteUrl(relativeUrl: string): Promise; getAliasedDataStoreEntryPoint(alias: string): Promise | undefined>; - // @deprecated - getRootDataStore(id: string, wait?: boolean): Promise; readonly isDirty: boolean; // (undocumented) readonly options: ILoaderOptions; - // @deprecated - resolveHandle(request: IRequest): Promise; // (undocumented) readonly scope: FluidObject; // (undocumented) diff --git a/packages/runtime/container-runtime-definitions/package.json b/packages/runtime/container-runtime-definitions/package.json index 243fe159f23c..43d415e2f968 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { @@ -70,6 +70,16 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_IContainerRuntime": { + "backCompat": false + }, + "TypeAliasDeclaration_IContainerRuntimeBaseWithCombinedEvents": { + "backCompat": false + }, + "InterfaceDeclaration_IContainerRuntimeWithResolveHandle_Deprecated": { + "backCompat": false + } + } } } diff --git a/packages/runtime/container-runtime-definitions/src/containerRuntime.ts b/packages/runtime/container-runtime-definitions/src/containerRuntime.ts index 36633bd74822..b8c0adf5394c 100644 --- a/packages/runtime/container-runtime-definitions/src/containerRuntime.ts +++ b/packages/runtime/container-runtime-definitions/src/containerRuntime.ts @@ -8,8 +8,6 @@ import { IEventProvider, IRequest, IResponse, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, FluidObject, IFluidHandle, IFluidHandleContext, @@ -72,15 +70,6 @@ export interface IContainerRuntime */ readonly attachState: AttachState; - /** - * Returns the runtime of the data store. - * @param id - Id supplied during creating the data store. - * @param wait - True if you want to wait for it. - * @deprecated Use getAliasedDataStoreEntryPoint instead to get an aliased data store's entry point. - */ - // eslint-disable-next-line import/no-deprecated - getRootDataStore(id: string, wait?: boolean): Promise; - /** * Returns the aliased data store's entryPoint, given the alias. * @param alias - The alias for the data store. @@ -112,11 +101,4 @@ export interface IContainerRuntime * @param relativeUrl - A relative request within the container */ getAbsoluteUrl(relativeUrl: string): Promise; - - /** - * Resolves handle URI - * @param request - request to resolve - * @deprecated Will be removed in future major release. Migrate all usage of resolveHandle to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - resolveHandle(request: IRequest): Promise; } 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 4e1ae4f8e46d..d596a28642b7 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 @@ -43,6 +43,7 @@ declare function get_current_InterfaceDeclaration_IContainerRuntime(): declare function use_old_InterfaceDeclaration_IContainerRuntime( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainerRuntime( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainerRuntime()); /* @@ -67,6 +68,7 @@ declare function get_current_TypeAliasDeclaration_IContainerRuntimeBaseWithCombi declare function use_old_TypeAliasDeclaration_IContainerRuntimeBaseWithCombinedEvents( use: TypeOnly): void; use_old_TypeAliasDeclaration_IContainerRuntimeBaseWithCombinedEvents( + // @ts-expect-error compatibility expected to be broken get_current_TypeAliasDeclaration_IContainerRuntimeBaseWithCombinedEvents()); /* @@ -115,4 +117,5 @@ declare function get_current_InterfaceDeclaration_IContainerRuntimeWithResolveHa declare function use_old_InterfaceDeclaration_IContainerRuntimeWithResolveHandle_Deprecated( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainerRuntimeWithResolveHandle_Deprecated( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainerRuntimeWithResolveHandle_Deprecated()); diff --git a/packages/runtime/container-runtime/api-report/container-runtime.api.md b/packages/runtime/container-runtime/api-report/container-runtime.api.md index 638aa115b402..1a813b32abf4 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -26,12 +26,10 @@ import { IFluidDataStoreContextDetached } from '@fluidframework/runtime-definiti import { IFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IFluidHandle } from '@fluidframework/core-interfaces'; import { IFluidHandleContext } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IGarbageCollectionData } from '@fluidframework/runtime-definitions'; import { IGetPendingLocalStateProps } from '@fluidframework/container-definitions'; import { IIdCompressor } from '@fluidframework/runtime-definitions'; import { IIdCompressorCore } from '@fluidframework/runtime-definitions'; -import { ILoader } from '@fluidframework/container-definitions'; import { ILoaderOptions } from '@fluidframework/container-definitions'; import { IProvideFluidHandleContext } from '@fluidframework/core-interfaces'; import { IQuorumClients } from '@fluidframework/protocol-definitions'; @@ -157,19 +155,13 @@ export class ContainerRuntime extends TypedEventEmitter; // (undocumented) getQuorum(): IQuorumClients; - // @deprecated - getRootDataStore(id: string, wait?: boolean): Promise; // (undocumented) idCompressor: (IIdCompressor & IIdCompressorCore) | undefined; // (undocumented) get IFluidDataStoreRegistry(): IFluidDataStoreRegistry; // (undocumented) get IFluidHandleContext(): IFluidHandleContext; - // @deprecated (undocumented) - get IFluidRouter(): this; get isDirty(): boolean; - // @deprecated (undocumented) - static load(context: IContainerContext, registryEntries: NamedFluidDataStoreRegistryEntries, requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise, runtimeOptions?: IContainerRuntimeOptions, containerScope?: FluidObject, existing?: boolean, containerRuntimeCtor?: typeof ContainerRuntime): Promise; static loadRuntime(params: { context: IContainerContext; registryEntries: NamedFluidDataStoreRegistryEntries; @@ -183,8 +175,6 @@ export class ContainerRuntime extends TypedEventEmitter; // (undocumented) readonly options: ILoaderOptions; @@ -195,8 +185,6 @@ export class ContainerRuntime extends TypedEventEmitter; - // @deprecated - request(request: IRequest): Promise; resolveHandle(request: IRequest): Promise; // (undocumented) get scope(): FluidObject; @@ -778,8 +766,6 @@ export class Summarizer extends TypedEventEmitter implements internalsProvider: ISummarizerInternalsProvider, handleContext: IFluidHandleContext, summaryCollection: SummaryCollection, runCoordinatorCreateFn: (runtime: IConnectableRuntime) => Promise); // (undocumented) close(): void; - // @deprecated - static create(loader: ILoader, url: string): Promise; dispose(): void; // (undocumented) enqueueSummarize(options: IEnqueueSummarizeOptions): EnqueueSummarizeResult; @@ -862,9 +848,6 @@ export class SummaryCollection extends TypedEventEmitter; - // @internal export const TombstoneResponseHeaderKey = "isTombstoned"; diff --git a/packages/runtime/container-runtime/package.json b/packages/runtime/container-runtime/package.json index adfd52f78f0c..6d8d8b765105 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container runtime", "homepage": "https://fluidframework.com", "repository": { @@ -132,6 +132,10 @@ "ClassDeclaration_ContainerRuntime": { "backCompat": false }, + "RemovedFunctionDeclaration_TEST_requestSummarizer": { + "forwardCompat": false, + "backCompat": false + }, "InterfaceDeclaration_IGCStats": { "forwardCompat": false } diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 2cd169a20f12..af2f2599305a 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -8,8 +8,6 @@ import { FluidObject, IFluidHandle, IFluidHandleContext, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IRequest, IResponse, IProvideFluidHandleContext, @@ -694,7 +692,9 @@ async function createSummarizer(loader: ILoader, url: string): Promise { - return createSummarizer(loader, url); -} - /** * Represents the runtime of the container. Contains helper functions/state of the container. * It will define the store level mappings. @@ -729,53 +721,6 @@ export class ContainerRuntime ISummarizerInternalsProvider, IProvideFluidHandleContext { - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this; - } - - /** - * @deprecated use loadRuntime instead. - * Load the stores from a snapshot and returns the runtime. - * @param context - Context of the container. - * @param registryEntries - Mapping to the stores. - * @param requestHandler - Request handlers for the container runtime - * @param runtimeOptions - Additional options to be passed to the runtime - * @param existing - (optional) When loading from an existing snapshot. Precedes context.existing if provided - * @param containerRuntimeCtor - (optional) Constructor to use to create the ContainerRuntime instance. This - * allows mixin classes to leverage this method to define their own async initializer. - */ - public static async load( - context: IContainerContext, - registryEntries: NamedFluidDataStoreRegistryEntries, - requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise, - runtimeOptions: IContainerRuntimeOptions = {}, - containerScope: FluidObject = context.scope, - existing?: boolean, - containerRuntimeCtor: typeof ContainerRuntime = ContainerRuntime, - ): Promise { - let existingFlag = true; - if (!existing) { - existingFlag = false; - } - return this.loadRuntime({ - context, - registryEntries, - existing: existingFlag, - runtimeOptions, - containerScope, - containerRuntimeCtor, - requestHandler, - provideEntryPoint: () => { - throw new UsageError( - "ContainerRuntime.load is deprecated and should no longer be used", - ); - }, - }); - } - /** * Load the stores from a snapshot and returns the runtime. * @param params - An object housing the runtime properties: @@ -798,7 +743,7 @@ export class ContainerRuntime runtimeOptions?: IContainerRuntimeOptions; containerScope?: FluidObject; containerRuntimeCtor?: typeof ContainerRuntime; - /** @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ + /** @deprecated Will be removed once Loader LTS version is "2.0.0-internal.7.0.0". Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md */ requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise; provideEntryPoint: (containerRuntime: IContainerRuntime) => Promise; }): Promise { @@ -1777,9 +1722,10 @@ export class ContainerRuntime /** * Notifies this object about the request made to the container. * @param request - Request made to the handler. - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md + * @deprecated Will be removed in future major release. This method needs to stay private until LTS version of Loader moves to "2.0.0-internal.7.0.0". */ - public async request(request: IRequest): Promise { + // @ts-expect-error expected to be used by LTS Loaders and Containers + private async request(request: IRequest): Promise { try { const parser = RequestParser.create(request); const id = parser.pathParts[0]; @@ -2438,28 +2384,6 @@ export class ContainerRuntime this.dataStores.processSignal(envelope.address, transformed, local); } - /** - * Returns the runtime of the data store. - * @param id - Id supplied during creating the data store. - * @param wait - True if you want to wait for it. - * @deprecated Use getAliasedDataStoreEntryPoint instead to get an aliased data store's entry point. - */ - // eslint-disable-next-line import/no-deprecated - public async getRootDataStore(id: string, wait = true): Promise { - return this.getRootDataStoreChannel(id, wait); - } - - private async getRootDataStoreChannel( - id: string, - wait = true, - ): Promise { - await this.dataStores.waitIfPendingAlias(id); - const internalId = this.internalId(id); - const context = await this.dataStores.getDataStore(internalId, { wait }); - assert(await context.isRoot(), 0x12b /* "did not get root data store" */); - return context.realize(); - } - /** * Flush the pending ops manually. * This method is expected to be called at the end of a batch. @@ -3952,8 +3876,6 @@ export class ContainerRuntime ); } - public notifyAttaching() {} // do nothing (deprecated method) - public async getPendingLocalState(props?: IGetPendingLocalStateProps): Promise { return PerformanceEvent.timedExecAsync( this.mc.logger, diff --git a/packages/runtime/container-runtime/src/dataStore.ts b/packages/runtime/container-runtime/src/dataStore.ts index c6795ad7e155..98e7b5063d0c 100644 --- a/packages/runtime/container-runtime/src/dataStore.ts +++ b/packages/runtime/container-runtime/src/dataStore.ts @@ -6,7 +6,7 @@ import { ITelemetryLoggerExt, TelemetryDataTag, UsageError } from "@fluidframework/telemetry-utils"; import { assert, unreachableCase } from "@fluidframework/core-utils"; import { AttachState } from "@fluidframework/container-definitions"; -import { FluidObject, IFluidHandle, IRequest, IResponse } from "@fluidframework/core-interfaces"; +import { FluidObject, IFluidHandle } from "@fluidframework/core-interfaces"; import { AliasResult, IDataStore, @@ -160,13 +160,6 @@ class DataStore implements IDataStore { return "Success"; } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public async request(request: IRequest): Promise { - return this.fluidDataStoreChannel.request(request); - } - /** * {@inheritDoc @fluidframework/runtime-definitions#IDataStore.entryPoint} */ @@ -184,13 +177,6 @@ class DataStore implements IDataStore { this.pendingAliases = datastores.pendingAliases; } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this.fluidDataStoreChannel; - } - private async ackBasedPromise( executor: ( resolve: (value: T | PromiseLike) => void, diff --git a/packages/runtime/container-runtime/src/id-compressor/idCompressor.ts b/packages/runtime/container-runtime/src/id-compressor/idCompressor.ts index 90001686a8be..60989fc6286b 100644 --- a/packages/runtime/container-runtime/src/id-compressor/idCompressor.ts +++ b/packages/runtime/container-runtime/src/id-compressor/idCompressor.ts @@ -16,7 +16,6 @@ import { SessionId, SessionSpaceCompressedId, StableId, - initialClusterCapacity, } from "@fluidframework/runtime-definitions"; import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces"; import { ITelemetryLoggerExt, createChildLogger } from "@fluidframework/telemetry-utils"; @@ -80,15 +79,21 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { // The gen count to be annotated on the range returned by the next call to `takeNextCreationRange`. // This is updated to be equal to `generatedIdCount` + 1 each time it is called. private nextRangeBaseGenCount = 1; - // The capacity of the next cluster to be created - private newClusterCapacity = initialClusterCapacity; private readonly sessions = new Sessions(); private readonly finalSpace = new FinalSpace(); // ----------------------- - // ----- Telemetry state ----- + // ----- Ephemeral state ----- + /** + * Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs. + * Eventually, this can be adjusted dynamically to have cluster reservation policies that + * optimize the number of eager finals. + * It is not readonly as it is accessed by tests for clear-box testing. + */ + // eslint-disable-next-line @typescript-eslint/prefer-readonly + private nextRequestedClusterSize: number = 512; // The number of local IDs generated since the last telemetry was sent. private telemetryLocalIdCount = 0; // The number of eager final IDs generated since the last telemetry was sent. @@ -143,27 +148,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { return compressor; } - /** - * The size of each newly created ID cluster. - */ - public get clusterCapacity(): number { - return this.newClusterCapacity; - } - - /** - * Must only be set with a value upon which consensus has been reached. Value must be greater than zero and less than - * `IdCompressor.maxClusterSize`. - */ - public set clusterCapacity(value: number) { - if (value <= 0) { - throw new Error("Clusters must have a positive capacity."); - } - if (value > IdCompressor.maxClusterSize) { - throw new Error("Clusters must not exceed max cluster size."); - } - this.newClusterCapacity = value; - } - public generateCompressedId(): SessionSpaceCompressedId { this.localGenCount++; const lastCluster = this.localSession.getLastCluster(); @@ -207,21 +191,36 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { ids: { firstGenCount: this.nextRangeBaseGenCount, count, + requestedClusterSize: this.nextRequestedClusterSize, }, }; this.nextRangeBaseGenCount = this.localGenCount + 1; + IdCompressor.assertValidRange(range); return range; } + private static assertValidRange(range: IdCreationRange): void { + if (range.ids === undefined) { + return; + } + const { count, requestedClusterSize } = range.ids; + assert(count > 0, 0x755 /* Malformed ID Range. */); + assert(requestedClusterSize > 0, "Clusters must have a positive capacity."); + assert( + requestedClusterSize <= IdCompressor.maxClusterSize, + "Clusters must not exceed max cluster size.", + ); + } + public finalizeCreationRange(range: IdCreationRange): void { // Check if the range has IDs if (range.ids === undefined) { return; } - assert(range.ids.count > 0, 0x755 /* Malformed ID Range. */); + IdCompressor.assertValidRange(range); const { sessionId, ids } = range; - const { count, firstGenCount } = ids; + const { count, firstGenCount, requestedClusterSize } = ids; const session = this.sessions.getOrCreate(sessionId); const isLocal = session === this.localSession; const rangeBaseLocal = localIdFromGenCount(firstGenCount); @@ -231,7 +230,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { if (rangeBaseLocal !== -1) { throw new Error("Ranges finalized out of order."); } - lastCluster = this.addEmptyCluster(session, this.clusterCapacity + count); + lastCluster = this.addEmptyCluster(session, requestedClusterSize + count); if (isLocal) { this.logger?.sendTelemetryEvent({ eventName: "RuntimeIdCompressor:FirstCluster", @@ -250,7 +249,7 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { lastCluster.count += count; } else { const overflow = count - remainingCapacity; - const newClaimedFinalCount = overflow + this.clusterCapacity; + const newClaimedFinalCount = overflow + requestedClusterSize; if (lastCluster === this.finalSpace.getLastCluster()) { // The last cluster in the sessions chain is the last cluster globally, so it can be expanded. lastCluster.capacity += newClaimedFinalCount; @@ -480,7 +479,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { const totalSize = 1 + // version 1 + // hasLocalState - 1 + // cluster capacity 1 + // session count 1 + // cluster count sessionIndexMap.size * 2 + // session IDs @@ -492,7 +490,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { let index = 0; index = writeNumber(serializedFloat, index, currentWrittenVersion); index = writeBoolean(serializedFloat, index, hasLocalState); - index = writeNumber(serializedFloat, index, this.clusterCapacity); index = writeNumber(serializedFloat, index, sessionIndexMap.size); index = writeNumber(serializedFloat, index, finalSpace.clusters.length); @@ -549,7 +546,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { const version = readNumber(index); assert(version === currentWrittenVersion, 0x75c /* Unknown serialized version. */); const hasLocalState = readBoolean(index); - const clusterCapacity = readNumber(index); const sessionCount = readNumber(index); const clusterCount = readNumber(index); @@ -575,7 +571,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { } const compressor = new IdCompressor(new Sessions(sessions)); - compressor.clusterCapacity = clusterCapacity; // Clusters let baseFinalId = 0; @@ -622,7 +617,6 @@ export class IdCompressor implements IIdCompressor, IIdCompressorCore { return false; } return ( - this.newClusterCapacity === other.newClusterCapacity && this.sessions.equals(other.sessions, includeLocalState) && this.finalSpace.equals(other.finalSpace) ); diff --git a/packages/runtime/container-runtime/src/index.ts b/packages/runtime/container-runtime/src/index.ts index ecca2516e576..0cb83c590af3 100644 --- a/packages/runtime/container-runtime/src/index.ts +++ b/packages/runtime/container-runtime/src/index.ts @@ -23,7 +23,6 @@ export { DefaultSummaryConfiguration, ICompressionRuntimeOptions, CompressionAlgorithms, - TEST_requestSummarizer, } from "./containerRuntime"; export { ContainerMessageType, diff --git a/packages/runtime/container-runtime/src/packageVersion.ts b/packages/runtime/container-runtime/src/packageVersion.ts index 5ec5780e3cb9..bb6ac249d1db 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/runtime/container-runtime/src/summary/summarizer.ts b/packages/runtime/container-runtime/src/summary/summarizer.ts index 8b36c4009f7f..a51eab4981ce 100644 --- a/packages/runtime/container-runtime/src/summary/summarizer.ts +++ b/packages/runtime/container-runtime/src/summary/summarizer.ts @@ -13,13 +13,9 @@ import { UsageError, wrapErrorAndLog, } from "@fluidframework/telemetry-utils"; -import { ILoader, LoaderHeader } from "@fluidframework/container-definitions"; -import { DriverHeader } from "@fluidframework/driver-definitions"; -import { FluidObject, IFluidHandleContext, IRequest } from "@fluidframework/core-interfaces"; -import { responseToException } from "@fluidframework/runtime-utils"; +import { IFluidHandleContext } from "@fluidframework/core-interfaces"; import { ISummaryConfiguration } from "../containerRuntime"; import { ICancellableSummarizerController } from "./runWhileConnectedCoordinator"; -import { summarizerClientType } from "./summarizerClientElection"; import { SummaryCollection } from "./summaryCollection"; import { RunningSummarizer } from "./runningSummarizer"; import { @@ -103,51 +99,6 @@ export class Summarizer extends TypedEventEmitter implements this.logger = createChildLogger({ logger: this.runtime.logger, namespace: "Summarizer" }); } - /** - * Creates a Summarizer and its underlying client. - * Note that different implementations of ILoader will handle the URL differently. - * ILoader provided by a ContainerRuntime is a RelativeLoader, which will treat URL's - * starting with "/" as relative to the Container. The general ILoader - * interface will expect an absolute URL and will not handle "/". - * @param loader - the loader that resolves the request - * @param url - the URL used to resolve the container - * @deprecated Creating a summarizer is not a publicly supported API. Please remove all usage of this static method. - */ - public static async create(loader: ILoader, url: string): Promise { - const request: IRequest = { - headers: { - [LoaderHeader.cache]: false, - [LoaderHeader.clientDetails]: { - capabilities: { interactive: false }, - type: summarizerClientType, - }, - [DriverHeader.summarizingClient]: true, - [LoaderHeader.reconnect]: false, - }, - url, - }; - - const resolvedContainer = await loader.resolve(request); - let fluidObject: FluidObject | undefined; - - // Older containers may not have the "getEntryPoint" API - // ! This check will need to stay until LTS of loader moves past 2.0.0-internal.7.0.0 - if (resolvedContainer.getEntryPoint !== undefined) { - fluidObject = await resolvedContainer.getEntryPoint(); - } else { - const response = await resolvedContainer.request({ url: "_summarizer" }); - if (response.status !== 200 || response.mimeType !== "fluid/object") { - throw responseToException(response, request); - } - fluidObject = response.value; - } - - if (fluidObject?.ISummarizer === undefined) { - throw new UsageError("Fluid object does not implement ISummarizer"); - } - return fluidObject.ISummarizer; - } - public async run(onBehalfOf: string): Promise { try { return await this.runCore(onBehalfOf); diff --git a/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts b/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts index e16b4626f7d0..139578c63e73 100644 --- a/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts +++ b/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts @@ -1300,7 +1300,9 @@ describe("Runtime", () => { }); // Calling request on the runtime should use the request handler we passed in the runtime's constructor. - const responseFromRequestMethod = await containerRuntime.request({ url: "/" }); + const responseFromRequestMethod = await (containerRuntime as any).request({ + url: "/", + }); assert.deepEqual( responseFromRequestMethod, myResponse, diff --git a/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.perf.spec.ts b/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.perf.spec.ts index d85afaece1e2..bc5ebd13beae 100644 --- a/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.perf.spec.ts +++ b/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.perf.spec.ts @@ -14,7 +14,6 @@ import { SessionSpaceCompressedId, SessionId, StableId, - initialClusterCapacity, } from "@fluidframework/runtime-definitions"; import { take } from "@fluid-private/stochastic-test-utils"; import { IdCompressor, createSessionId } from "../../id-compressor"; @@ -29,6 +28,8 @@ import { sessionIds, } from "./idCompressorTestUtilities"; +const initialClusterCapacity = 512; + describe("IdCompressor Perf", () => { const type = BenchmarkType.Measurement; const localClient = Client.Client1; @@ -41,7 +42,7 @@ describe("IdCompressor Perf", () => { synchronizeAtEnd: boolean, ): IdCompressorTestNetwork { const perfNetwork = new IdCompressorTestNetwork(clusterSize); - const maxClusterSize = 25; + const maxClusterSize = clusterSize * 2; const generator = take( 1000, makeOpGenerator({ @@ -50,9 +51,6 @@ describe("IdCompressor Perf", () => { outsideAllocationFraction: 0.9, }), ); - if (perfNetwork.initialClusterSize > maxClusterSize) { - perfNetwork.enqueueCapacityChange(maxClusterSize); - } performFuzzActions( generator, perfNetwork, @@ -151,6 +149,7 @@ describe("IdCompressor Perf", () => { ids: { firstGenCount, count: numIds, + requestedClusterSize: initialClusterCapacity, }, }; @@ -224,8 +223,16 @@ describe("IdCompressor Perf", () => { const network = setupCompressors(initialClusterCapacity, false, true); // Ensure the local session has several different clusters for (let clusterCount = 0; clusterCount < 5; clusterCount++) { - network.allocateAndSendIds(localClient, perfCompressor.clusterCapacity); - network.allocateAndSendIds(remoteClient, perfCompressor.clusterCapacity * 2); + network.allocateAndSendIds( + localClient, + // eslint-disable-next-line @typescript-eslint/dot-notation + perfCompressor["nextRequestedClusterSize"], + ); + network.allocateAndSendIds( + remoteClient, + // eslint-disable-next-line @typescript-eslint/dot-notation + perfCompressor["nextRequestedClusterSize"] * 2, + ); network.deliverOperations(DestinationClient.All); } const client = isLocalOriginator ? localClient : remoteClient; @@ -247,7 +254,8 @@ describe("IdCompressor Perf", () => { // Ensure no eager finals network.allocateAndSendIds( localClient, - network.getCompressor(localClient).clusterCapacity * 2 + 1, + // eslint-disable-next-line @typescript-eslint/dot-notation + network.getCompressor(localClient)["nextRequestedClusterSize"] * 2 + 1, ); unackedLocalId = getIdMadeBy(localClient, false, network); assert( diff --git a/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.spec.ts b/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.spec.ts index 8a84c92899be..2bfa93c0fb69 100644 --- a/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.spec.ts +++ b/packages/runtime/container-runtime/src/test/id-compressor/idCompressor.spec.ts @@ -29,22 +29,6 @@ import { import { LocalCompressedId, incrementStableId, isFinalId, isLocalId, fail } from "./testCommon"; describe("IdCompressor", () => { - it("detects invalid cluster sizes", () => { - const compressor = CompressorFactory.createCompressor(Client.Client1, 1); - assert.throws( - () => (compressor.clusterCapacity = -1), - (e: Error) => validateAssertionError(e, "Clusters must have a positive capacity."), - ); - assert.throws( - () => (compressor.clusterCapacity = 0), - (e: Error) => validateAssertionError(e, "Clusters must have a positive capacity."), - ); - assert.throws( - () => (compressor.clusterCapacity = 2 ** 20 + 1), - (e: Error) => validateAssertionError(e, "Clusters must not exceed max cluster size."), - ); - }); - it("reports the proper session ID", () => { const sessionId = createSessionId(); const compressor = CompressorFactory.createCompressorWithSession(sessionId); @@ -712,7 +696,7 @@ describe("IdCompressor", () => { mockLogger.assertMatchAny([ { eventName: "RuntimeIdCompressor:SerializedIdCompressorSize", - size: 80, + size: 72, clusterCount: 1, sessionCount: 1, }, @@ -979,23 +963,6 @@ describe("IdCompressor", () => { network.deliverOperations(DestinationClient.All); }); - itNetwork("can set the cluster size via API", 2, (network) => { - const compressor = network.getCompressor(Client.Client1); - const compressor2 = network.getCompressor(Client.Client2); - const initialClusterCapacity = compressor.clusterCapacity; - network.allocateAndSendIds(Client.Client1, 1); - network.allocateAndSendIds(Client.Client2, 1); - network.enqueueCapacityChange(5); - network.allocateAndSendIds(Client.Client1, 3); - const opSpaceIds = network.allocateAndSendIds(Client.Client2, 3); - network.deliverOperations(DestinationClient.All); - // Glass box test, as it knows the order of final IDs - assert.equal( - compressor.normalizeToSessionSpace(opSpaceIds[2], compressor2.localSessionId), - (initialClusterCapacity + 1) * 2 + compressor.clusterCapacity + 1, - ); - }); - itNetwork("does not decompress ids for empty parts of clusters", 2, (network) => { // This is a glass box test in that it creates a final ID outside of the ID compressor network.allocateAndSendIds(Client.Client1, 1); @@ -1015,14 +982,19 @@ describe("IdCompressor", () => { network.allocateAndSendIds(Client.Client1, 3); network.allocateAndSendIds( Client.Client2, - network.getCompressor(Client.Client2).clusterCapacity * 2, + + // eslint-disable-next-line @typescript-eslint/dot-notation + network.getCompressor(Client.Client2)["nextRequestedClusterSize"] * 2, ); network.allocateAndSendIds(Client.Client3, 5); expectSequencedLogsAlign(network, Client.Client1, Client.Client2); }); itNetwork("can finalize a range when the current cluster is full", 5, (network) => { - const clusterCapacity = network.getCompressor(Client.Client1).clusterCapacity; + const clusterCapacity = network.getCompressor( + Client.Client1, + // eslint-disable-next-line @typescript-eslint/dot-notation + )["nextRequestedClusterSize"]; network.allocateAndSendIds(Client.Client1, clusterCapacity); network.allocateAndSendIds(Client.Client2, clusterCapacity); network.allocateAndSendIds(Client.Client1, clusterCapacity); @@ -1030,7 +1002,10 @@ describe("IdCompressor", () => { }); itNetwork("can finalize a range that spans multiple clusters", 5, (network) => { - const clusterCapacity = network.getCompressor(Client.Client1).clusterCapacity; + const clusterCapacity = network.getCompressor( + Client.Client1, + // eslint-disable-next-line @typescript-eslint/dot-notation + )["nextRequestedClusterSize"]; network.allocateAndSendIds(Client.Client1, 1); network.allocateAndSendIds(Client.Client2, 1); network.allocateAndSendIds(Client.Client1, clusterCapacity * 3); @@ -1104,31 +1079,14 @@ describe("IdCompressor", () => { expectSerializes(network.getCompressor(Client.Client3)); }); - // TODO: test in Rust - // itNetwork( - // "packs IDs into a single cluster when a single client generates non-overridden ids", - // 3, - // (network) => { - // network.allocateAndSendIds(Client.Client1, 20); - // network.deliverOperations(DestinationClient.All); - // const [serialized1WithNoSession, serialized1WithSession] = expectSerializes( - // network.getCompressor(Client.Client1), - // ); - // assert.equal(serialized1WithNoSession.clusters.length, 1); - // assert.equal(serialized1WithSession.clusters.length, 1); - // const [serialized3WithNoSession, serialized3WithSession] = expectSerializes( - // network.getCompressor(Client.Client3), - // ); - // assert.equal(serialized3WithNoSession.clusters.length, 1); - // assert.equal(serialized3WithSession.clusters.length, 1); - // }, - // ); - itNetwork( "can resume a session and interact with multiple other clients", 3, (network) => { - const clusterSize = network.getCompressor(Client.Client1).clusterCapacity; + const clusterSize = network.getCompressor( + Client.Client1, + // eslint-disable-next-line @typescript-eslint/dot-notation + )["nextRequestedClusterSize"]; network.allocateAndSendIds(Client.Client1, clusterSize); network.allocateAndSendIds(Client.Client2, clusterSize); network.allocateAndSendIds(Client.Client3, clusterSize); diff --git a/packages/runtime/container-runtime/src/test/id-compressor/idCompressorTestUtilities.ts b/packages/runtime/container-runtime/src/test/id-compressor/idCompressorTestUtilities.ts index 4998e8af1a8c..469d19a9a48d 100644 --- a/packages/runtime/container-runtime/src/test/id-compressor/idCompressorTestUtilities.ts +++ b/packages/runtime/container-runtime/src/test/id-compressor/idCompressorTestUtilities.ts @@ -97,7 +97,8 @@ export class CompressorFactory { logger?: ITelemetryBaseLogger, ): IdCompressor { const compressor = IdCompressor.create(sessionId, logger); - compressor.clusterCapacity = clusterCapacity; + // eslint-disable-next-line @typescript-eslint/dot-notation + compressor["nextRequestedClusterSize"] = clusterCapacity; return compressor; } } @@ -129,6 +130,7 @@ export function buildHugeCompressor( ids: { firstGenCount: Math.floor(i / numSessions) * capacity + 1, count: capacity, + requestedClusterSize: capacity, }, }); } @@ -173,15 +175,12 @@ export class IdCompressorTestNetwork { /** The compressors used in this network */ private readonly compressors: ClientMap; /** The log of operations seen by the server so far. Append-only. */ - private readonly serverOperations: ( - | [ - creationRange: IdCreationRange, - opSpaceIds: OpSpaceCompressedId[], - clientFrom: OriginatingClient, - sessionIdFrom: SessionId, - ] - | number - )[] = []; + private readonly serverOperations: [ + creationRange: IdCreationRange, + opSpaceIds: OpSpaceCompressedId[], + clientFrom: OriginatingClient, + sessionIdFrom: SessionId, + ][] = []; /** An index into `serverOperations` for each client which represents how many operations have been delivered to that client */ private readonly clientProgress: ClientMap; /** All ids (local and sequenced) that a client has created or received, in order. */ @@ -276,10 +275,11 @@ export class IdCompressorTestNetwork { } /** - * Submit a capacity change operation to the network. It will not take effect immediately but will be processed in sequence order. + * Changes the capacity request amount for a client. It will take effect immediately. */ - public enqueueCapacityChange(newClusterCapacity: number): void { - this.serverOperations.push(newClusterCapacity); + public changeCapacity(client: Client, newClusterCapacity: number): void { + // eslint-disable-next-line @typescript-eslint/dot-notation + this.compressors.get(client)["nextRequestedClusterSize"] = newClusterCapacity; } private addNewId( @@ -330,6 +330,10 @@ export class IdCompressorTestNetwork { ids: { firstGenCount: 1, count: numIds, + requestedClusterSize: this.getCompressor(Client.Client1)[ + // eslint-disable-next-line @typescript-eslint/dot-notation + "nextRequestedClusterSize" + ], }, }; const opSpaceIds: OpSpaceCompressedId[] = []; @@ -353,17 +357,17 @@ export class IdCompressorTestNetwork { } /** - * Delivers all undelivered ID ranges and cluster capacity changes from the server to the target clients. + * Delivers all undelivered ID ranges from the server to the target clients. */ public deliverOperations(clientTakingDelivery: Client, opsToDeliver?: number): void; /** - * Delivers all undelivered ID ranges and cluster capacity changes from the server to the target clients. + * Delivers all undelivered ID ranges from the server to the target clients. */ public deliverOperations(clientTakingDelivery: DestinationClient): void; /** - * Delivers all undelivered ID ranges and cluster capacity changes from the server to the target clients. + * Delivers all undelivered ID ranges from the server to the target clients. */ public deliverOperations(clientTakingDelivery: DestinationClient, opsToDeliver?: number): void { let opIndexBound: number; @@ -378,28 +382,17 @@ export class IdCompressorTestNetwork { } for (const [clientTo, compressorTo] of this.getTargetCompressors(clientTakingDelivery)) { for (let i = this.clientProgress.get(clientTo); i < opIndexBound; i++) { - const operation = this.serverOperations[i]; - if (typeof operation === "number") { - compressorTo.clusterCapacity = operation; - } else { - const [range, opSpaceIds, clientFrom, sessionIdFrom] = operation; - compressorTo.finalizeCreationRange(range); - - const ids = range.ids; - if (ids !== undefined) { - for (const id of opSpaceIds) { - const sessionSpaceId = compressorTo.normalizeToSessionSpace( - id, - range.sessionId, - ); - this.addNewId( - clientTo, - sessionSpaceId, - clientFrom, - sessionIdFrom, - true, - ); - } + const [range, opSpaceIds, clientFrom, sessionIdFrom] = this.serverOperations[i]; + compressorTo.finalizeCreationRange(range); + + const ids = range.ids; + if (ids !== undefined) { + for (const id of opSpaceIds) { + const sessionSpaceId = compressorTo.normalizeToSessionSpace( + id, + range.sessionId, + ); + this.addNewId(clientTo, sessionSpaceId, clientFrom, sessionIdFrom, true); } } } @@ -662,6 +655,7 @@ interface DeliverSomeOperations { interface ChangeCapacity { type: "changeCapacity"; + client: Client; newSize: number; } @@ -743,9 +737,10 @@ export function makeOpGenerator( }; } - function changeCapacityGenerator({ random }: FuzzTestState): ChangeCapacity { + function changeCapacityGenerator({ random, activeClients }: FuzzTestState): ChangeCapacity { return { type: "changeCapacity", + client: random.pick(activeClients), newSize: Math.min( Math.floor(random.real(0, 1) ** 2 * maxClusterSize) + 1, maxClusterSize, @@ -846,7 +841,7 @@ export function performFuzzActions( return state; }, changeCapacity: (state, op) => { - network.enqueueCapacityChange(op.newSize); + network.changeCapacity(op.client, op.newSize); return { ...state, clusterSize: op.newSize }; }, deliverSomeOperations: (state, op) => { diff --git a/packages/runtime/container-runtime/src/test/id-compressor/testCommon.ts b/packages/runtime/container-runtime/src/test/id-compressor/testCommon.ts index c5a0682e309b..7326ba7d3a72 100644 --- a/packages/runtime/container-runtime/src/test/id-compressor/testCommon.ts +++ b/packages/runtime/container-runtime/src/test/id-compressor/testCommon.ts @@ -98,16 +98,13 @@ export function incrementStableId(stableId: StableId, offset: number): StableId } /** An immutable view of an `IdCompressor` */ -export interface ReadonlyIdCompressor - extends Omit< - IdCompressor, - | "generateCompressedId" - | "generateCompressedIdRange" - | "takeNextCreationRange" - | "finalizeCreationRange" - > { - readonly clusterCapacity: number; -} +export type ReadonlyIdCompressor = Omit< + IdCompressor, + | "generateCompressedId" + | "generateCompressedIdRange" + | "takeNextCreationRange" + | "finalizeCreationRange" +>; /** * Asserts a value is not undefined, and returns the value. 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 f98fc2ede1e9..b9c44f65a235 100644 --- a/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts +++ b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts @@ -1874,26 +1874,14 @@ use_old_TypeAliasDeclaration_SummaryStage( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_TEST_requestSummarizer": {"forwardCompat": false} +* "RemovedFunctionDeclaration_TEST_requestSummarizer": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_TEST_requestSummarizer(): - TypeOnly; -declare function use_current_FunctionDeclaration_TEST_requestSummarizer( - use: TypeOnly): void; -use_current_FunctionDeclaration_TEST_requestSummarizer( - get_old_FunctionDeclaration_TEST_requestSummarizer()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_TEST_requestSummarizer": {"backCompat": false} +* "RemovedFunctionDeclaration_TEST_requestSummarizer": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_TEST_requestSummarizer(): - TypeOnly; -declare function use_old_FunctionDeclaration_TEST_requestSummarizer( - use: TypeOnly): void; -use_old_FunctionDeclaration_TEST_requestSummarizer( - get_current_FunctionDeclaration_TEST_requestSummarizer()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md b/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md index a8647251db26..18244e13f80d 100644 --- a/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md +++ b/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md @@ -16,15 +16,12 @@ import { IExperimentalIncrementalSummaryContext } from '@fluidframework/runtime- import { IFluidHandle } from '@fluidframework/core-interfaces'; import { IFluidHandleContext } from '@fluidframework/core-interfaces'; import { IFluidLoadable } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IGarbageCollectionData } from '@fluidframework/runtime-definitions'; import { IIdCompressor } from '@fluidframework/runtime-definitions'; import { IInboundSignalMessage } from '@fluidframework/runtime-definitions'; import { ILoaderOptions } from '@fluidframework/container-definitions'; import { IProvideFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IQuorumClients } from '@fluidframework/protocol-definitions'; -import { IRequest } from '@fluidframework/core-interfaces'; -import { IResponse } from '@fluidframework/core-interfaces'; import { ISequencedDocumentMessage } from '@fluidframework/protocol-definitions'; import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions'; import { ITelemetryContext } from '@fluidframework/runtime-definitions'; @@ -115,16 +112,12 @@ export interface IFluidDataStoreRuntime extends IEventProvider; // (undocumented) readonly rootRoutingContext: IFluidHandleContext; submitSignal(type: string, content: any, targetClientId?: string): void; @@ -144,12 +137,21 @@ export interface IFluidDataStoreRuntimeEvents extends IEvent { (event: "connected", listener: (clientId: string) => void): any; } +// @alpha (undocumented) +export interface Internal_InterfaceOfJsonableTypesWith { + // (undocumented) + [index: string | number]: JsonableTypeWith; +} + // @alpha -export type Jsonable = T extends undefined | null | boolean | number | string | TReplaced ? T : Extract extends never ? { +export type Jsonable = boolean extends (T extends never ? true : false) ? JsonableTypeWith : unknown extends T ? JsonableTypeWith : T extends undefined | null | boolean | number | string | TReplaced ? T : Extract extends never ? T extends object ? T extends (infer U)[] ? Jsonable[] : { [K in keyof T]: Extract extends never ? Jsonable : never; -} : never; +} : never : never; + +// @alpha +export type JsonableTypeWith = undefined | null | boolean | number | string | T | Internal_InterfaceOfJsonableTypesWith | ArrayLike>; // @alpha -export type Serializable = Jsonable; +export type Serializable = Jsonable; ``` diff --git a/packages/runtime/datastore-definitions/package.json b/packages/runtime/datastore-definitions/package.json index 038826b20352..91d8e3c8944f 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid data store definitions", "homepage": "https://fluidframework.com", "repository": { @@ -69,6 +69,10 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_IFluidDataStoreRuntime": { + "backCompat": false + } + } } } diff --git a/packages/runtime/datastore-definitions/src/dataStoreRuntime.ts b/packages/runtime/datastore-definitions/src/dataStoreRuntime.ts index a51999576e34..e46c304124c4 100644 --- a/packages/runtime/datastore-definitions/src/dataStoreRuntime.ts +++ b/packages/runtime/datastore-definitions/src/dataStoreRuntime.ts @@ -9,12 +9,8 @@ import { ITelemetryLogger, IDisposable, IFluidHandleContext, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IFluidHandle, FluidObject, - IRequest, - IResponse, } from "@fluidframework/core-interfaces"; import { IAudience, @@ -141,15 +137,4 @@ export interface IFluidDataStoreRuntime * with it. */ readonly entryPoint: IFluidHandle; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - request(request: IRequest): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - readonly IFluidRouter: IFluidRouter; } diff --git a/packages/runtime/datastore-definitions/src/index.ts b/packages/runtime/datastore-definitions/src/index.ts index 7309e865a5df..dea71ef04fe6 100644 --- a/packages/runtime/datastore-definitions/src/index.ts +++ b/packages/runtime/datastore-definitions/src/index.ts @@ -19,6 +19,6 @@ export { IDeltaHandler, } from "./channel"; export { IFluidDataStoreRuntime, IFluidDataStoreRuntimeEvents } from "./dataStoreRuntime"; -export { Jsonable } from "./jsonable"; +export type { Jsonable, JsonableTypeWith, Internal_InterfaceOfJsonableTypesWith } from "./jsonable"; export { Serializable } from "./serializable"; export { IChannelAttributes } from "./storage"; diff --git a/packages/runtime/datastore-definitions/src/jsonable.ts b/packages/runtime/datastore-definitions/src/jsonable.ts index e314df973e0d..774cc8912acc 100644 --- a/packages/runtime/datastore-definitions/src/jsonable.ts +++ b/packages/runtime/datastore-definitions/src/jsonable.ts @@ -3,6 +3,43 @@ * Licensed under the MIT License. */ +/** + * Type constraint for types that are likely serializable as JSON or have a custom + * alternate type. + * + * @remarks + * Use `JsonableTypeWith` for just JSON serializable types. + * See {@link Jsonable} for serialization pitfalls. + * + * @privateRemarks + * Perfer using `Jsonable` over this type that is an implementation detail. + * @alpha + */ +export type JsonableTypeWith = + | undefined + | null + | boolean + | number + | string + | T + | Internal_InterfaceOfJsonableTypesWith + | ArrayLike>; + +/** + * @remarks + * This type is a kludge and not intended for general use. + * + * @privateRemarks + * Internal type testing for compatibility uses TypeOnly filter which cannot handle recursive "pure" types. + * This interface along with ArrayLike above avoids pure type recursion issues, but introduces a limitation on + * the ability of {@link Jsonable} to detect array-like types that are not handled naively ({@link JSON.stringify}). + * The TypeOnly filter is not useful for {@link JsonableTypeWith}; so, if type testing improves, this can be removed. + * @alpha + */ +export interface Internal_InterfaceOfJsonableTypesWith { + [index: string | number]: JsonableTypeWith; +} + /** * Used to constrain a type `T` to types that are serializable as JSON. * Produces a compile-time error if `T` contains non-Jsonable members. @@ -10,12 +47,11 @@ * @remarks * Note that this does NOT prevent using of values with non-json compatible data, * it only prevents using values with types that include non-json compatible data. - * This means that one can, for example, pass an a value typed with json compatible + * This means that one can, for example, pass in a value typed with json compatible * interface into this function, * that could actually be a class with lots on non-json compatible fields and methods. * * Important: `T extends Jsonable` is incorrect (does not even compile). - * `T extends Jsonable` is also incorrect since `Jsonable` is just `any` and thus applies no constraint at all. * * The optional 'TReplaced' parameter may be used to permit additional leaf types to support * situations where a `replacer` is used to handle special values (e.g., `Jsonable<{ x: IFluidHandle }, IFluidHandle>`). @@ -30,10 +66,12 @@ * * - prototypes and non-enumerable properties are lost. * + * - `ArrayLike` types that are not arrays and are serialized as `{ length: number }`. + * * Also, `Jsonable` does not prevent the construction of circular references. * - * Using `Jsonable` (with no type parameters) or `Jsonable` is just a type alias for `any` - * and should not be used if type safety is desired. + * Using `Jsonable` or `Jsonable` is a type alias for + * {@link JsonableTypeWith}`` and should not be used if precise type safety is desired. * * @example Typical usage * @@ -42,17 +80,29 @@ * ``` * @alpha */ -export type Jsonable = T extends - | undefined - | null - | boolean - | number - | string - | TReplaced - ? T +export type Jsonable = /* test for 'any' */ boolean extends ( + T extends never ? true : false +) + ? /* 'any' => */ JsonableTypeWith + : /* test for 'unknown' */ unknown extends T + ? /* 'unknown' => */ JsonableTypeWith + : /* test for Jsonable primitive types */ T extends + | undefined /* is not serialized */ + | null + | boolean + | number + | string + | TReplaced + ? /* primitive types => */ T : // eslint-disable-next-line @typescript-eslint/ban-types - Extract extends never - ? { - [K in keyof T]: Extract extends never ? Jsonable : never; - } - : never; + /* test for not a function */ Extract extends never + ? /* not a function => => test for object */ T extends object + ? /* object => test for array */ T extends (infer U)[] // prefer ArrayLike test to catch non-array array-like types + ? /* array => */ Jsonable[] + : /* property bag => */ { + [K in keyof T]: Extract extends never + ? Jsonable + : never; + } + : /* not an object => */ never + : /* function => */ never; diff --git a/packages/runtime/datastore-definitions/src/serializable.ts b/packages/runtime/datastore-definitions/src/serializable.ts index b9b06a819980..36521c08c157 100644 --- a/packages/runtime/datastore-definitions/src/serializable.ts +++ b/packages/runtime/datastore-definitions/src/serializable.ts @@ -24,4 +24,4 @@ import { Jsonable } from "./jsonable"; * ``` * @alpha */ -export type Serializable = Jsonable; +export type Serializable = Jsonable; diff --git a/packages/runtime/datastore-definitions/src/test/types/jsonable.ts b/packages/runtime/datastore-definitions/src/test/types/jsonable.ts index f7620e018a42..504304a79b16 100644 --- a/packages/runtime/datastore-definitions/src/test/types/jsonable.ts +++ b/packages/runtime/datastore-definitions/src/test/types/jsonable.ts @@ -55,10 +55,26 @@ interface IA2 { declare const a2: IA2; foo(a2); -// text complex indexed interface -declare const a3: { [key: string]: Jsonable }; +// test complex indexed type +declare const a3: { [key: string]: string }; foo(a3); +// test "unknown" cannonical Json content +type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; +declare const json: Json; +foo(json); + +// test "unknown" Jsonable content +declare const unknownJsonable: Jsonable; +foo(unknownJsonable); + +// test "unknown" cannonical Json content in an interface +interface A4 { + payload: Json; +} +declare const a4: A4; +foo(a4); + // test interface with multiple properties interface A5 { a: "a"; @@ -100,8 +116,31 @@ const nested: INested = { }; foo(nested); +// test `any` type +declare const anAny: any; +foo(anAny); + +// test "recursive" type compiles +// Infinite recursion not supported nor desired but is not prevented +// and this test exists simply to demonstrate that limitation. +interface SelfReferencing { + me: SelfReferencing; +} +declare const selfReferencing: SelfReferencing; +foo(selfReferencing); + // --- should not work +// test unknown +declare const aUnknown: unknown; +// @ts-expect-error should not be jsonable +foo(aUnknown); + +// test interface with unknown +declare const nestedUnknown: { a: unknown }; +// @ts-expect-error should not be jsonable +foo(nestedUnknown); + // test interface with method, and member interface IA11 { ["a"]: "a"; @@ -129,7 +168,7 @@ foo(a13); // test type with primative and object with classes union interface IA14 { - a: number | Date; + a: number | bar; } declare const a14: IA14; // @ts-expect-error should not be jsonable @@ -172,3 +211,109 @@ const isym: ISymbol = { }; // @ts-expect-error should not be jsonable foo(isym); + +// *disabled* test that array-like types are fully arrays +// Jsonable allows ArrayLike to be an object as base JsonableTypeWith +// uses ArrayLike to avoid TypeOnly type test limitation. If that is addressed +// then this test should be enabled. +declare const mayNotBeArray: ArrayLike; +// @disable ts-expect-error should not be jsonable +foo(mayNotBeArray); + +/** + * TypeAliasOf creates a type equivalent version of an interface. + * @remarks + * It is used below to bypass the early "Index signature for type 'string'" issue for interfaces. + */ +type TypeAliasOf = T extends object + ? T extends () => any | null + ? T + : { [K in keyof T]: TypeAliasOf } + : T; + +// test foo(value: Jsonable) remains an error for troublesome values even when coercing T to `any` +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(aUnknown); +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(nestedUnknown); +declare const a11t: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(a11t); +declare const a12t: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(a12t); +declare const a13t: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(a13t); +declare const a14t: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(a14t); +const aBar = new bar(); +declare const aBarT: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(aBarT); +declare const anMtT: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(anMtT); +declare const anNmtT: TypeAliasOf; +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(anNmtT); +// @ts-expect-error unsupported types cannot circumnavigate with Jsonable +foo(isym); + +// --- tests that are not part of the Jsonable specification but are included +// to demonstrate limitations. + +// test interfaces passed for Jsonable are not assignable +// Typescript makes an allowance for types regarding index signatures but does not for +// interfaces which may be augmented. Test that +// Index signature for type 'string' is missing in type 'X'. ts(2345) +// occurs for interfaces but not for types. +// See https://github.com/microsoft/TypeScript/issues/15300#issuecomment-657218214 +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a2); +// no error for _type_ to Jsonable +foo(a3); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a4); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a5); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a6); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(a7); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(nested); +// no error for any to Jsonable +foo(anAny); +// @ts-expect-error interfaces are not assignable to Jsonable +foo(selfReferencing); + +// Show that cast to Jsonable version of self (a type alias) does compile +// to be passed to Jsonable. This is not a recommended practice. +foo(a as Jsonable); +foo(a2 as Jsonable); +foo(a3 as Jsonable); +foo(a4 as Jsonable); +foo(a5 as Jsonable); +foo(a6 as Jsonable); +foo(a7 as Jsonable); +foo(nested as Jsonable); +foo(anAny as Jsonable); +foo(selfReferencing as Jsonable); + +// Show that cast to Jsonable version of self does compile even if not +// respecting the limitations. This is a dangerous practice, but can +// be useful if very careful. +foo(aUnknown as Jsonable); +foo(nestedUnknown as Jsonable); +foo(a11 as Jsonable); +foo(a12 as Jsonable); +foo(a13 as Jsonable); +foo(a14 as Jsonable); +foo(aBar as Jsonable); +foo(mt as Jsonable); +foo(nmt as Jsonable); +foo(isym as Jsonable); diff --git a/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts b/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts index f9441dfdb0cc..1d393b2d6fcc 100644 --- a/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts +++ b/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts @@ -211,6 +211,7 @@ declare function get_current_InterfaceDeclaration_IFluidDataStoreRuntime(): declare function use_old_InterfaceDeclaration_IFluidDataStoreRuntime( use: TypeOnly): void; use_old_InterfaceDeclaration_IFluidDataStoreRuntime( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IFluidDataStoreRuntime()); /* @@ -245,7 +246,7 @@ use_old_InterfaceDeclaration_IFluidDataStoreRuntimeEvents( declare function get_old_TypeAliasDeclaration_Jsonable(): TypeOnly; declare function use_current_TypeAliasDeclaration_Jsonable( - use: TypeOnly): void; + use: TypeOnly>): void; use_current_TypeAliasDeclaration_Jsonable( get_old_TypeAliasDeclaration_Jsonable()); @@ -255,7 +256,7 @@ use_current_TypeAliasDeclaration_Jsonable( * "TypeAliasDeclaration_Jsonable": {"backCompat": false} */ declare function get_current_TypeAliasDeclaration_Jsonable(): - TypeOnly; + TypeOnly>; declare function use_old_TypeAliasDeclaration_Jsonable( use: TypeOnly): void; use_old_TypeAliasDeclaration_Jsonable( @@ -269,7 +270,7 @@ use_old_TypeAliasDeclaration_Jsonable( declare function get_old_TypeAliasDeclaration_Serializable(): TypeOnly; declare function use_current_TypeAliasDeclaration_Serializable( - use: TypeOnly): void; + use: TypeOnly>): void; use_current_TypeAliasDeclaration_Serializable( get_old_TypeAliasDeclaration_Serializable()); @@ -279,7 +280,7 @@ use_current_TypeAliasDeclaration_Serializable( * "TypeAliasDeclaration_Serializable": {"backCompat": false} */ declare function get_current_TypeAliasDeclaration_Serializable(): - TypeOnly; + TypeOnly>; declare function use_old_TypeAliasDeclaration_Serializable( use: TypeOnly): void; use_old_TypeAliasDeclaration_Serializable( diff --git a/packages/runtime/datastore/api-report/datastore.api.md b/packages/runtime/datastore/api-report/datastore.api.md index 90d14e1e9da7..8504e2da0e21 100644 --- a/packages/runtime/datastore/api-report/datastore.api.md +++ b/packages/runtime/datastore/api-report/datastore.api.md @@ -88,12 +88,8 @@ export class FluidDataStoreRuntime extends TypedEventEmitter implements IFluidDataStoreChannel, IFluidDataStoreRuntime, IFluidHandleContext { - /** - * @deprecated Instantiate the class using its constructor instead. - * - * Loads the data store runtime - * @param context - The data store context - * @param sharedObjectRegistry - The registry of shared objects used by this data store - * @param existing - If loading from an existing file. - */ - public static load( - context: IFluidDataStoreContext, - sharedObjectRegistry: ISharedObjectRegistry, - existing: boolean, - ): FluidDataStoreRuntime { - return new FluidDataStoreRuntime( - context, - sharedObjectRegistry, - existing, - async (dataStoreRuntime) => dataStoreRuntime.entryPoint, - ); - } - /** * {@inheritDoc @fluidframework/datastore-definitions#IFluidDataStoreRuntime.entryPoint} */ public readonly entryPoint: IFluidHandle; - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this; - } - public get connected(): boolean { return this.dataStoreContext.connected; } diff --git a/packages/runtime/datastore/src/localChannelContext.ts b/packages/runtime/datastore/src/localChannelContext.ts index 0b411bf476a1..79b04c8a7a10 100644 --- a/packages/runtime/datastore/src/localChannelContext.ts +++ b/packages/runtime/datastore/src/localChannelContext.ts @@ -17,6 +17,7 @@ import { } from "@fluidframework/runtime-definitions"; import { assert, Lazy, LazyPromise } from "@fluidframework/core-utils"; import { IFluidHandle } from "@fluidframework/core-interfaces"; +import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions"; import { ChannelServiceEndpoints, createChannelServiceEndpoints, @@ -245,18 +246,19 @@ export class RehydratedLocalChannelContext extends LocalChannelContextBase { } private isSnapshotInOldFormatAndCollectBlobs( - snapshotTree: ISnapshotTree, + snapshotTree: ISnapshotTreeWithBlobContents, blobMap: Map, ): boolean { let sanitize = false; - const blobsContents: { [path: string]: ArrayBufferLike } = (snapshotTree as any) - .blobsContents; - Object.entries(blobsContents).forEach(([key, value]) => { - blobMap.set(key, value); - if (snapshotTree.blobs[key] !== undefined) { - sanitize = true; - } - }); + const blobsContents = snapshotTree.blobsContents; + if (blobsContents !== undefined) { + Object.entries(blobsContents).forEach(([key, value]) => { + blobMap.set(key, value); + if (snapshotTree.blobs[key] !== undefined) { + sanitize = true; + } + }); + } for (const value of Object.values(snapshotTree.trees)) { sanitize = sanitize || this.isSnapshotInOldFormatAndCollectBlobs(value, blobMap); } diff --git a/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts b/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts index 9fac738dc501..d7fcaebb2862 100644 --- a/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts +++ b/packages/runtime/datastore/src/test/types/validateDatastorePrevious.generated.ts @@ -68,6 +68,7 @@ declare function get_current_ClassDeclaration_FluidDataStoreRuntime(): declare function use_old_ClassDeclaration_FluidDataStoreRuntime( use: TypeOnly): void; use_old_ClassDeclaration_FluidDataStoreRuntime( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_FluidDataStoreRuntime()); /* diff --git a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md index d4126fa95e5a..11d87d41154b 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -15,8 +15,6 @@ import { IDocumentStorageService } from '@fluidframework/driver-definitions'; import { IEvent } from '@fluidframework/core-interfaces'; import { IEventProvider } from '@fluidframework/core-interfaces'; import { IFluidHandle } from '@fluidframework/core-interfaces'; -import { IFluidHandleContext } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { ILoaderOptions } from '@fluidframework/container-definitions'; import { IProvideFluidHandleContext } from '@fluidframework/core-interfaces'; import { IQuorumClients } from '@fluidframework/protocol-definitions'; @@ -126,13 +124,9 @@ export interface IContainerRuntimeBase extends IEventProvider; getAudience(): IAudience; getQuorum(): IQuorumClients; - // @deprecated (undocumented) - readonly IFluidHandleContext: IFluidHandleContext; // (undocumented) readonly logger: ITelemetryBaseLogger; orderSequentially(callback: () => void): void; - // @deprecated - request(request: IRequest): Promise; submitSignal(type: string, content: any): void; // (undocumented) uploadBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise>; @@ -153,15 +147,6 @@ export interface IContainerRuntimeBaseEvents extends IEvent { // @alpha export interface IDataStore { readonly entryPoint: IFluidHandle; - // @deprecated (undocumented) - readonly IFluidRouter: IFluidRouter; - // @deprecated (undocumented) - request(request: { - url: "/"; - headers?: undefined; - }): Promise; - // @deprecated - request(request: IRequest): Promise; trySetAlias(alias: string): Promise; } @@ -171,6 +156,7 @@ export interface IdCreationRange { readonly ids?: { readonly firstGenCount: number; readonly count: number; + readonly requestedClusterSize: number; }; // (undocumented) readonly sessionId: SessionId; @@ -201,8 +187,6 @@ export interface IFluidDataStoreChannel extends IDisposable { getGCData(fullGC?: boolean): Promise; // (undocumented) readonly id: string; - // @deprecated (undocumented) - readonly IFluidRouter: IFluidRouter; makeVisibleAndAttachGraph(): void; process(message: ISequencedDocumentMessage, local: boolean, localOpMetadata: unknown): void; processSignal(message: any, local: boolean): void; @@ -339,9 +323,6 @@ export type InboundAttachMessage = Omit & { snapshot: IAttachMessage["snapshot"] | null; }; -// @internal -export const initialClusterCapacity = 512; - // @alpha (undocumented) export interface IProvideFluidDataStoreFactory { // (undocumented) diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index 5f69ae49d754..dfe89e841f0c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { @@ -78,6 +78,32 @@ "RemovedTypeAliasDeclaration_IdCreationRangeWithStashedState": { "forwardCompat": false, "backCompat": false + }, + "InterfaceDeclaration_IdCreationRange": { + "forwardCompat": false + }, + "VariableDeclaration_initialClusterCapacity": { + "backCompat": false, + "forwardCompat": false + }, + "RemovedVariableDeclaration_initialClusterCapacity": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_IContainerRuntimeBase": { + "backCompat": false + }, + "InterfaceDeclaration_IFluidDataStoreContext": { + "backCompat": false + }, + "InterfaceDeclaration_IFluidDataStoreContextDetached": { + "backCompat": false + }, + "InterfaceDeclaration_IDataStore": { + "backCompat": false + }, + "InterfaceDeclaration_IFluidDataStoreChannel": { + "backCompat": false } } } diff --git a/packages/runtime/runtime-definitions/src/dataStoreContext.ts b/packages/runtime/runtime-definitions/src/dataStoreContext.ts index 7192f18421e3..ea2a524c4b91 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -8,14 +8,11 @@ import { IEventProvider, ITelemetryBaseLogger, IDisposable, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IProvideFluidHandleContext, IFluidHandle, IRequest, IResponse, FluidObject, - IFluidHandleContext, } from "@fluidframework/core-interfaces"; import { IAudience, @@ -160,38 +157,6 @@ export interface IDataStore { * with it. */ readonly entryPoint: IFluidHandle; - - /** - * @deprecated Requesting will not be supported in a future major release. - * Instead, access the objects within the DataStore using entryPoint, and then navigate from there using - * app-specific logic (e.g. retrieving a handle from a DDS, or the entryPoint object could implement a request paradigm itself) - * - * IMPORTANT: This overload is provided for back-compat where IDataStore.request(\{ url: "/" \}) is already implemented and used. - * The functionality it can provide (if the DataStore implementation is built for it) is redundant with @see {@link IDataStore.entryPoint}. - * - * Refer to Removing-IFluidRouter.md for details on migrating from the request pattern to using entryPoint. - * - * @param request - Only requesting \{ url: "/" \} is supported, requesting arbitrary URLs is deprecated. - */ - request(request: { url: "/"; headers?: undefined }): Promise; - - /** - * Issue a request against the DataStore for a resource within it. - * @param request - The request to be issued against the DataStore - * - * @deprecated Requesting an arbitrary URL with headers will not be supported in a future major release. - * Instead, access the objects within the DataStore using entryPoint, and then navigate from there using - * app-specific logic (e.g. retrieving a handle from a DDS, or the entryPoint object could implement a request paradigm itself) - * - * Refer to Removing-IFluidRouter.md for details on migrating from the request pattern to using entryPoint. - */ - request(request: IRequest): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - readonly IFluidRouter: IFluidRouter; } /** @@ -209,12 +174,6 @@ export interface IContainerRuntimeBase extends IEventProvider void): void; - /** - * Executes a request against the container runtime - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - request(request: IRequest): Promise; - /** * Submits a container runtime level signal to be sent to other clients. * @param type - Type of the signal. @@ -265,11 +224,6 @@ export interface IContainerRuntimeBase extends IEventProvider; request(request: IRequest): Promise; - - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - // eslint-disable-next-line import/no-deprecated - readonly IFluidRouter: IFluidRouter; } /** diff --git a/packages/runtime/runtime-definitions/src/id-compressor/index.ts b/packages/runtime/runtime-definitions/src/id-compressor/index.ts index 5f76802a7471..34943c7f9e77 100644 --- a/packages/runtime/runtime-definitions/src/id-compressor/index.ts +++ b/packages/runtime/runtime-definitions/src/id-compressor/index.ts @@ -8,7 +8,6 @@ export { SerializedIdCompressor, SerializedIdCompressorWithNoSession, SerializedIdCompressorWithOngoingSession, - initialClusterCapacity, } from "./persisted-types"; export { IIdCompressorCore, IIdCompressor } from "./idCompressor"; diff --git a/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/0.0.1.ts b/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/0.0.1.ts index de5b569e3ce0..843cedde711f 100644 --- a/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/0.0.1.ts +++ b/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/0.0.1.ts @@ -38,14 +38,21 @@ export type SerializedIdCompressorWithOngoingSession = SerializedIdCompressor & export interface IdCreationRange { readonly sessionId: SessionId; readonly ids?: { + /** + * The gen count of the first ID in the range created by `sessionId.` + */ readonly firstGenCount: number; + + /** + * The number of IDs created in the range created by `sessionId.` + */ readonly count: number; + + /** + * The size of the ID cluster to create if `count` overflows the existing cluster for + * `sessionId`, if one exists. This request will be respected, and the size of the cluster + * will be equal to overflow + `requestedClusterSize`. + */ + readonly requestedClusterSize: number; }; } - -/** - * Roughly equates to a minimum of 1M sessions before we start allocating 64 bit IDs. - * This value must *NOT* change without careful consideration to compatibility. - * @internal - */ -export const initialClusterCapacity = 512; diff --git a/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/index.ts b/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/index.ts index 2f16094a9b95..5f72ae45dcdd 100644 --- a/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/index.ts +++ b/packages/runtime/runtime-definitions/src/id-compressor/persisted-types/index.ts @@ -8,5 +8,4 @@ export { SerializedIdCompressor, SerializedIdCompressorWithNoSession, SerializedIdCompressorWithOngoingSession, - initialClusterCapacity, } from "./0.0.1"; diff --git a/packages/runtime/runtime-definitions/src/index.ts b/packages/runtime/runtime-definitions/src/index.ts index 802298b8dfb6..a1c758636439 100644 --- a/packages/runtime/runtime-definitions/src/index.ts +++ b/packages/runtime/runtime-definitions/src/index.ts @@ -76,5 +76,4 @@ export { SessionId, StableId, IdCreationRange, - initialClusterCapacity, } from "./id-compressor"; diff --git a/packages/runtime/runtime-definitions/src/test/types/requestSignatureDeprecation.ts b/packages/runtime/runtime-definitions/src/test/types/requestSignatureDeprecation.ts deleted file mode 100644 index b9ecc03e48cd..000000000000 --- a/packages/runtime/runtime-definitions/src/test/types/requestSignatureDeprecation.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/* eslint-disable @typescript-eslint/no-floating-promises */ -/* eslint deprecation/deprecation: "error" */ - -import { IDataStore } from "../../dataStoreContext"; - -declare const dataStore: IDataStore; - -// These are deprecated -// eslint-disable-next-line deprecation/deprecation -dataStore.request({ url: "/" }); -// eslint-disable-next-line deprecation/deprecation -dataStore.request({ url: "/", headers: { shouldBeDeprecated: true } }); -// eslint-disable-next-line deprecation/deprecation -dataStore.request({ url: "/should/be/deprecated" }); 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 b7ca074e212a..551e8692477f 100644 --- a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts @@ -307,6 +307,7 @@ declare function get_current_InterfaceDeclaration_IContainerRuntimeBase(): declare function use_old_InterfaceDeclaration_IContainerRuntimeBase( use: TypeOnly): void; use_old_InterfaceDeclaration_IContainerRuntimeBase( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IContainerRuntimeBase()); /* @@ -355,6 +356,7 @@ declare function get_current_InterfaceDeclaration_IDataStore(): declare function use_old_InterfaceDeclaration_IDataStore( use: TypeOnly): void; use_old_InterfaceDeclaration_IDataStore( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IDataStore()); /* @@ -427,6 +429,7 @@ declare function get_current_InterfaceDeclaration_IFluidDataStoreChannel(): declare function use_old_InterfaceDeclaration_IFluidDataStoreChannel( use: TypeOnly): void; use_old_InterfaceDeclaration_IFluidDataStoreChannel( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IFluidDataStoreChannel()); /* @@ -451,6 +454,7 @@ declare function get_current_InterfaceDeclaration_IFluidDataStoreContext(): declare function use_old_InterfaceDeclaration_IFluidDataStoreContext( use: TypeOnly): void; use_old_InterfaceDeclaration_IFluidDataStoreContext( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IFluidDataStoreContext()); /* @@ -475,6 +479,7 @@ declare function get_current_InterfaceDeclaration_IFluidDataStoreContextDetached declare function use_old_InterfaceDeclaration_IFluidDataStoreContextDetached( use: TypeOnly): void; use_old_InterfaceDeclaration_IFluidDataStoreContextDetached( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IFluidDataStoreContextDetached()); /* @@ -1015,6 +1020,7 @@ declare function get_old_InterfaceDeclaration_IdCreationRange(): declare function use_current_InterfaceDeclaration_IdCreationRange( use: TypeOnly): void; use_current_InterfaceDeclaration_IdCreationRange( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_IdCreationRange()); /* @@ -1548,26 +1554,14 @@ use_old_VariableDeclaration_gcTreeKey( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_initialClusterCapacity": {"forwardCompat": false} +* "RemovedVariableDeclaration_initialClusterCapacity": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_initialClusterCapacity(): - TypeOnly; -declare function use_current_VariableDeclaration_initialClusterCapacity( - use: TypeOnly): void; -use_current_VariableDeclaration_initialClusterCapacity( - get_old_VariableDeclaration_initialClusterCapacity()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "VariableDeclaration_initialClusterCapacity": {"backCompat": false} +* "RemovedVariableDeclaration_initialClusterCapacity": {"backCompat": false} */ -declare function get_current_VariableDeclaration_initialClusterCapacity(): - TypeOnly; -declare function use_old_VariableDeclaration_initialClusterCapacity( - use: TypeOnly): void; -use_old_VariableDeclaration_initialClusterCapacity( - get_current_VariableDeclaration_initialClusterCapacity()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/runtime/runtime-utils/api-report/runtime-utils.api.md b/packages/runtime/runtime-utils/api-report/runtime-utils.api.md index 251f22d31702..bbe9664c977f 100644 --- a/packages/runtime/runtime-utils/api-report/runtime-utils.api.md +++ b/packages/runtime/runtime-utils/api-report/runtime-utils.api.md @@ -4,14 +4,12 @@ ```ts -import { FluidObject } from '@fluidframework/core-interfaces'; import { IChannelStorageService } from '@fluidframework/datastore-definitions'; import { IContainerContext } from '@fluidframework/container-definitions'; import { IContainerRuntime } from '@fluidframework/container-runtime-definitions'; import { IFluidDataStoreFactory } from '@fluidframework/runtime-definitions'; import { IFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IFluidHandleContext } from '@fluidframework/core-interfaces'; -import { IFluidRouter } from '@fluidframework/core-interfaces'; import { IGarbageCollectionData } from '@fluidframework/runtime-definitions'; import { IProvideFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IRequest } from '@fluidframework/core-interfaces'; @@ -20,6 +18,7 @@ import { IResponse } from '@fluidframework/core-interfaces'; import { IRuntime } from '@fluidframework/container-definitions'; import { IRuntimeFactory } from '@fluidframework/container-definitions'; import { ISnapshotTree } from '@fluidframework/protocol-definitions'; +import { ISnapshotTreeWithBlobContents } from '@fluidframework/container-definitions'; import { ISummarizeResult } from '@fluidframework/runtime-definitions'; import { ISummaryBlob } from '@fluidframework/protocol-definitions'; import { ISummaryStats } from '@fluidframework/runtime-definitions'; @@ -44,7 +43,7 @@ export function addTreeToSummary(summary: ISummaryTreeWithStats, key: string, su export function calculateStats(summary: SummaryObject): ISummaryStats; // @internal -export function convertSnapshotTreeToSummaryTree(snapshot: ISnapshotTree): ISummaryTreeWithStats; +export function convertSnapshotTreeToSummaryTree(snapshot: ISnapshotTreeWithBlobContents): ISummaryTreeWithStats; // @internal export function convertSummaryTreeToITree(summaryTree: ISummaryTree): ITree; @@ -121,9 +120,6 @@ export class ObjectStoragePartition implements IChannelStorageService { // @internal export type ReadAndParseBlob = (id: string) => Promise; -// @internal @deprecated (undocumented) -export function requestFluidObject(router: IFluidRouter, url: string | IRequest): Promise; - // @alpha export class RequestParser implements IRequest { protected constructor(request: Readonly); diff --git a/packages/runtime/runtime-utils/package.json b/packages/runtime/runtime-utils/package.json index 24e1be4bc1aa..a3832c75c517 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Collection of utility functions for Fluid Runtime", "homepage": "https://fluidframework.com", "repository": { @@ -113,6 +113,11 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedFunctionDeclaration_requestFluidObject": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/runtime/runtime-utils/src/dataStoreHelpers.ts b/packages/runtime/runtime-utils/src/dataStoreHelpers.ts index 78510251d479..99d645e0f7ee 100644 --- a/packages/runtime/runtime-utils/src/dataStoreHelpers.ts +++ b/packages/runtime/runtime-utils/src/dataStoreHelpers.ts @@ -4,8 +4,7 @@ */ import { assert } from "@fluidframework/core-utils"; -// eslint-disable-next-line import/no-deprecated -import { FluidObject, IFluidRouter, IRequest, IResponse } from "@fluidframework/core-interfaces"; +import { IRequest, IResponse } from "@fluidframework/core-interfaces"; import { IFluidDataStoreFactory, IFluidDataStoreRegistry, @@ -72,26 +71,6 @@ export function responseToException(response: IResponse, request: IRequest): Err return responseErr; } -/** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @internal - */ -export async function requestFluidObject( - // eslint-disable-next-line import/no-deprecated - router: IFluidRouter, - url: string | IRequest, -): Promise { - const request = typeof url === "string" ? { url } : url; - const response = await router.request(request); - - if (response.status !== 200 || response.mimeType !== "fluid/object") { - throw responseToException(response, request); - } - - assert(response.value, 0x19a /* "Invalid response value for Fluid object request" */); - return response.value as T; -} - /** * @internal */ diff --git a/packages/runtime/runtime-utils/src/index.ts b/packages/runtime/runtime-utils/src/index.ts index 841f65f2be79..9f99c90e377e 100644 --- a/packages/runtime/runtime-utils/src/index.ts +++ b/packages/runtime/runtime-utils/src/index.ts @@ -10,7 +10,6 @@ export { createResponseError, exceptionToResponse, Factory, - requestFluidObject, responseToException, } from "./dataStoreHelpers"; export { ObjectStoragePartition } from "./objectstoragepartition"; diff --git a/packages/runtime/runtime-utils/src/summaryUtils.ts b/packages/runtime/runtime-utils/src/summaryUtils.ts index 9f3300bbc12a..c269d63a9c8b 100644 --- a/packages/runtime/runtime-utils/src/summaryUtils.ts +++ b/packages/runtime/runtime-utils/src/summaryUtils.ts @@ -20,7 +20,6 @@ import { ISummaryBlob, TreeEntry, ITreeEntry, - ISnapshotTree, } from "@fluidframework/protocol-definitions"; import { ISummaryStats, @@ -29,6 +28,7 @@ import { ITelemetryContext, IGarbageCollectionData, } from "@fluidframework/runtime-definitions"; +import { ISnapshotTreeWithBlobContents } from "@fluidframework/container-definitions"; /** * Combines summary stats by adding their totals together. @@ -298,12 +298,14 @@ export function convertToSummaryTree(snapshot: ITree, fullTree: boolean = false) * @param snapshot - snapshot in ISnapshotTree format * @internal */ -export function convertSnapshotTreeToSummaryTree(snapshot: ISnapshotTree): ISummaryTreeWithStats { +export function convertSnapshotTreeToSummaryTree( + snapshot: ISnapshotTreeWithBlobContents, +): ISummaryTreeWithStats { const builder = new SummaryTreeBuilder(); for (const [path, id] of Object.entries(snapshot.blobs)) { let decoded: string | undefined; - if ((snapshot as any).blobsContents !== undefined) { - const content: ArrayBufferLike = (snapshot as any).blobsContents[id]; + if (snapshot.blobsContents !== undefined) { + const content: ArrayBufferLike = snapshot.blobsContents[id]; if (content !== undefined) { decoded = bufferToString(content, "utf-8"); } diff --git a/packages/runtime/runtime-utils/src/test/types/validateRuntimeUtilsPrevious.generated.ts b/packages/runtime/runtime-utils/src/test/types/validateRuntimeUtilsPrevious.generated.ts index 48c07e380bc3..fe1ee3fd7209 100644 --- a/packages/runtime/runtime-utils/src/test/types/validateRuntimeUtilsPrevious.generated.ts +++ b/packages/runtime/runtime-utils/src/test/types/validateRuntimeUtilsPrevious.generated.ts @@ -624,26 +624,14 @@ use_old_FunctionDeclaration_mergeStats( /* * Validate forward compat by using old type in place of current type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_requestFluidObject": {"forwardCompat": false} +* "RemovedFunctionDeclaration_requestFluidObject": {"forwardCompat": false} */ -declare function get_old_FunctionDeclaration_requestFluidObject(): - TypeOnly; -declare function use_current_FunctionDeclaration_requestFluidObject( - use: TypeOnly): void; -use_current_FunctionDeclaration_requestFluidObject( - get_old_FunctionDeclaration_requestFluidObject()); /* * Validate back compat by using current type in place of old type * If breaking change required, add in package.json under typeValidation.broken: -* "FunctionDeclaration_requestFluidObject": {"backCompat": false} +* "RemovedFunctionDeclaration_requestFluidObject": {"backCompat": false} */ -declare function get_current_FunctionDeclaration_requestFluidObject(): - TypeOnly; -declare function use_old_FunctionDeclaration_requestFluidObject( - use: TypeOnly): void; -use_old_FunctionDeclaration_requestFluidObject( - get_current_FunctionDeclaration_requestFluidObject()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.api.md b/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.api.md index 7bf3c115660c..fb849f3fbba6 100644 --- a/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.api.md +++ b/packages/runtime/test-runtime-utils/api-report/test-runtime-utils.api.md @@ -204,7 +204,7 @@ export class MockDeltaConnection implements IDeltaConnection { export class MockDeltaManager extends TypedEventEmitter implements IDeltaManager { constructor(); // (undocumented) - get active(): boolean; + readonly active: boolean; // (undocumented) readonly clientDetails: IClientDetails; // (undocumented) @@ -234,7 +234,7 @@ export class MockDeltaManager extends TypedEventEmitter imp // (undocumented) lastSequenceNumber: number; // (undocumented) - get maxMessageSize(): number; + readonly maxMessageSize: number; // (undocumented) minimumSequenceNumber: number; // (undocumented) @@ -441,8 +441,6 @@ export class MockFluidDataStoreRuntime extends EventEmitter implements IFluidDat readonly id: string; // (undocumented) get IFluidHandleContext(): IFluidHandleContext; - // @deprecated (undocumented) - get IFluidRouter(): this; // (undocumented) get isAttached(): boolean; // (undocumented) @@ -466,7 +464,7 @@ export class MockFluidDataStoreRuntime extends EventEmitter implements IFluidDat processSignal(message: any, local: boolean): void; // (undocumented) quorum: MockQuorumClients; - // @deprecated (undocumented) + // (undocumented) request(request: IRequest): Promise; // (undocumented) requestDataStore(request: IRequest): Promise; diff --git a/packages/runtime/test-runtime-utils/package.json b/packages/runtime/test-runtime-utils/package.json index ecee5faf8a53..35a637a32497 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid runtime test utilities", "homepage": "https://fluidframework.com", "repository": { @@ -115,6 +115,13 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_MockFluidDataStoreRuntime": { + "backCompat": false + }, + "ClassDeclaration_MockFluidDataStoreContext": { + "backCompat": false + } + } } } diff --git a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts index 9da338810507..2a3497ff6b32 100644 --- a/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts +++ b/packages/runtime/test-runtime-utils/src/assertionShortCodesMap.ts @@ -776,7 +776,7 @@ export const shortCodeMap = { "0x4ad": "Should not be called when connection is already present!", "0x4ae": "Connection should be disposed by now", "0x4af": "Received message from user not in the audience", - "0x4b0": "Encoded timestamps should be an array of nummbers", + "0x4b0": "Encoded timestamps should be an array of numbers", "0x4b1": "serialized attribution columns should have the same length", "0x4b2": "new client has different payload from existing one", "0x4b3": "have connection", diff --git a/packages/runtime/test-runtime-utils/src/mockDeltas.ts b/packages/runtime/test-runtime-utils/src/mockDeltas.ts index 60536b8de8b4..f046b66ae283 100644 --- a/packages/runtime/test-runtime-utils/src/mockDeltas.ts +++ b/packages/runtime/test-runtime-utils/src/mockDeltas.ts @@ -144,19 +144,13 @@ export class MockDeltaManager return undefined as any as string; } - // eslint-disable-next-line @typescript-eslint/class-literal-property-style - public get maxMessageSize(): number { - return 0; - } + public readonly maxMessageSize: number = 0; public get serviceConfiguration(): IClientConfiguration { return undefined as any as IClientConfiguration; } - // eslint-disable-next-line @typescript-eslint/class-literal-property-style - public get active(): boolean { - return true; - } + public readonly active: boolean = true; public close(): void {} diff --git a/packages/runtime/test-runtime-utils/src/mocks.ts b/packages/runtime/test-runtime-utils/src/mocks.ts index 12f823bbd93f..e7f33e877e47 100644 --- a/packages/runtime/test-runtime-utils/src/mocks.ts +++ b/packages/runtime/test-runtime-utils/src/mocks.ts @@ -616,13 +616,6 @@ export class MockFluidDataStoreRuntime return this; } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this; - } - public readonly documentId: string = undefined as any; public readonly id: string; public readonly existing: boolean = undefined as any; @@ -783,9 +776,6 @@ export class MockFluidDataStoreRuntime return this.request(request); } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ public async request(request: IRequest): Promise { return null as any as IResponse; } 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 a6fc27b58c7d..a57e8f34296a 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 @@ -331,6 +331,7 @@ declare function get_current_ClassDeclaration_MockFluidDataStoreContext(): declare function use_old_ClassDeclaration_MockFluidDataStoreContext( use: TypeOnly): void; use_old_ClassDeclaration_MockFluidDataStoreContext( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_MockFluidDataStoreContext()); /* @@ -355,6 +356,7 @@ declare function get_current_ClassDeclaration_MockFluidDataStoreRuntime(): declare function use_old_ClassDeclaration_MockFluidDataStoreRuntime( use: TypeOnly): void; use_old_ClassDeclaration_MockFluidDataStoreRuntime( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_MockFluidDataStoreRuntime()); /* 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 6fa6a52999a7..e50badceac5e 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Odsp client end to end tests", "homepage": "https://fluidframework.com", diff --git a/packages/service-clients/odsp-client/package.json b/packages/service-clients/odsp-client/package.json index 89be3561be15..55125bba517a 100644 --- a/packages/service-clients/odsp-client/package.json +++ b/packages/service-clients/odsp-client/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/odsp-client", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A tool to enable creation and loading of Fluid containers using the ODSP service", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/service-clients/odsp-client/src/odspClient.ts b/packages/service-clients/odsp-client/src/odspClient.ts index 9ddf0387665e..7f74c556a941 100644 --- a/packages/service-clients/odsp-client/src/odspClient.ts +++ b/packages/service-clients/odsp-client/src/odspClient.ts @@ -193,13 +193,11 @@ export class OdspClient { } private async getContainerEntryPoint(container: IContainer): Promise { - const rootDataObject: FluidObject | undefined = - await container.getEntryPoint(); - assert(rootDataObject !== undefined, "entryPoint must exist"); - // ! This "if" is needed for back-compat (older instances of IRootDataObject may not have the IRootDataObject property) - if (rootDataObject.IRootDataObject === undefined) { - return rootDataObject as IRootDataObject; - } + const rootDataObject: FluidObject = await container.getEntryPoint(); + assert( + rootDataObject.IRootDataObject !== undefined, + "entryPoint must be of type IRootDataObject", + ); return rootDataObject.IRootDataObject; } } diff --git a/packages/test/functional-tests/package.json b/packages/test/functional-tests/package.json index 4503d1d86419..7932074d354d 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Functional tests", "homepage": "https://fluidframework.com", diff --git a/packages/test/local-server-tests/package.json b/packages/test/local-server-tests/package.json index f3e7a2abc813..09bfc8db7040 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Tests that can only run against the local server", "homepage": "https://fluidframework.com", diff --git a/packages/test/local-server-tests/src/test/connectionMode.spec.ts b/packages/test/local-server-tests/src/test/connectionMode.spec.ts index 567665aba46d..b84a09ebb886 100644 --- a/packages/test/local-server-tests/src/test/connectionMode.spec.ts +++ b/packages/test/local-server-tests/src/test/connectionMode.spec.ts @@ -7,7 +7,6 @@ import { strict as assert } from "assert"; import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct"; import { IContainer, IFluidCodeDetails } from "@fluidframework/container-definitions"; import { ConnectionState, Loader } from "@fluidframework/container-loader"; -import { IRequest } from "@fluidframework/core-interfaces"; import { LocalDocumentServiceFactory, LocalResolver } from "@fluidframework/local-driver"; import { SharedMap } from "@fluidframework/map"; import { @@ -23,7 +22,6 @@ import { TestFluidObjectFactory, } from "@fluidframework/test-utils"; import { MockLogger } from "@fluidframework/telemetry-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; describe("Logging Last Connection Mode ", () => { const documentId = "connectionModeTest"; @@ -68,12 +66,9 @@ describe("Logging Last Connection Mode ", () => { "default", ); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], }); const urlResolver = new LocalResolver(); diff --git a/packages/test/local-server-tests/src/test/documentDirty.spec.ts b/packages/test/local-server-tests/src/test/documentDirty.spec.ts index 7729507aab1d..530f018483d2 100644 --- a/packages/test/local-server-tests/src/test/documentDirty.spec.ts +++ b/packages/test/local-server-tests/src/test/documentDirty.spec.ts @@ -8,7 +8,6 @@ import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqu import { IContainer, IFluidCodeDetails } from "@fluidframework/container-definitions"; import { ConnectionState, Loader } from "@fluidframework/container-loader"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IRequest } from "@fluidframework/core-interfaces"; import { LocalDocumentServiceFactory, LocalResolver } from "@fluidframework/local-driver"; import { SharedMap } from "@fluidframework/map"; import { @@ -23,7 +22,6 @@ import { LocalCodeLoader, TestFluidObjectFactory, } from "@fluidframework/test-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; describe("Document Dirty", () => { const documentId = "documentDirtyTest"; @@ -118,12 +116,9 @@ describe("Document Dirty", () => { "default", ); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], }); const urlResolver = new LocalResolver(); @@ -440,12 +435,9 @@ describe("Document Dirty", () => { "default", ); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], }); const urlResolver = new LocalResolver(); diff --git a/packages/test/local-server-tests/src/test/noDeltaStream.spec.ts b/packages/test/local-server-tests/src/test/noDeltaStream.spec.ts index 5195ca03e14c..07ca88e702c6 100644 --- a/packages/test/local-server-tests/src/test/noDeltaStream.spec.ts +++ b/packages/test/local-server-tests/src/test/noDeltaStream.spec.ts @@ -25,7 +25,6 @@ import { } from "@fluidframework/test-utils"; import { IDocumentServiceFactory } from "@fluidframework/driver-definitions"; import { DeltaStreamConnectionForbiddenError } from "@fluidframework/driver-utils"; -import { rootDataStoreRequestHandler } from "@fluidframework/request-handler"; import { ConnectionState } from "@fluidframework/container-loader"; describe("No Delta Stream", () => { @@ -40,7 +39,6 @@ describe("No Delta Stream", () => { "", new TestFluidObjectFactory([[stringId, SharedString.getFactory()]]), {}, - [rootDataStoreRequestHandler], ); let deltaConnectionServer: ILocalDeltaConnectionServer; 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 174566061488..99fd37436b3f 100644 --- a/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts +++ b/packages/test/local-server-tests/src/test/opsOnReconnect.spec.ts @@ -8,11 +8,11 @@ import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqu import { IContainer, IHostLoader, IFluidCodeDetails } from "@fluidframework/container-definitions"; import { ConnectionState, Loader } from "@fluidframework/container-loader"; import { ContainerMessageType, IContainerRuntimeOptions } from "@fluidframework/container-runtime"; -import { IFluidHandle, IFluidLoadable, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidHandle, IFluidLoadable } from "@fluidframework/core-interfaces"; import { LocalDocumentServiceFactory, LocalResolver } from "@fluidframework/local-driver"; import { SharedMap, SharedDirectory } from "@fluidframework/map"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IEnvelope, FlushMode, IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; +import { IEnvelope, FlushMode } from "@fluidframework/runtime-definitions"; import { createDataStoreFactory } from "@fluidframework/runtime-utils"; import { ILocalDeltaConnectionServer, @@ -62,15 +62,12 @@ describe("Ops on Reconnect", () => { const defaultFactory = createDataStoreFactory("default", factory); const dataObject2Factory = createDataStoreFactory("dataObject2", factory); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [ [defaultFactory.type, Promise.resolve(defaultFactory)], [dataObject2Factory.type, Promise.resolve(dataObject2Factory)], ], - requestHandlers: [innerRequestHandler], runtimeOptions, }); diff --git a/packages/test/mocha-test-setup/package.json b/packages/test/mocha-test-setup/package.json index ea8a32021e8b..c1df83d9b69e 100644 --- a/packages/test/mocha-test-setup/package.json +++ b/packages/test/mocha-test-setup/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/mocha-test-setup", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Utilities for Fluid tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/mocha-test-setup/src/packageVersion.ts b/packages/test/mocha-test-setup/src/packageVersion.ts index 72e74a22f64d..a76c494138e8 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 = "@fluidframework/mocha-test-setup"; -export const pkgVersion = "2.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/snapshots/package.json b/packages/test/snapshots/package.json index 66539338bb88..26b16892ce8b 100644 --- a/packages/test/snapshots/package.json +++ b/packages/test/snapshots/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-snapshots", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Comprehensive test of snapshot logic.", "homepage": "https://fluidframework.com", diff --git a/packages/test/snapshots/src/packageVersion.ts b/packages/test/snapshots/src/packageVersion.ts index a65569cc59b0..aa085e916706 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/stochastic-test-utils/package.json b/packages/test/stochastic-test-utils/package.json index 1944823b078d..808e850cf1b4 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Utilities for stochastic tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-app-insights-logger/package.json b/packages/test/test-app-insights-logger/package.json index 3859edbba3be..9eb4d864dcde 100644 --- a/packages/test/test-app-insights-logger/package.json +++ b/packages/test/test-app-insights-logger/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-app-insights-logger", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Azure Application Insights logger for Fluid tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-driver-definitions/package.json b/packages/test/test-driver-definitions/package.json index 5474bf865bee..85cfc26dc612 100644 --- a/packages/test/test-driver-definitions/package.json +++ b/packages/test/test-driver-definitions/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/test-driver-definitions", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A driver abstraction and implementations for testing against server", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-drivers/package.json b/packages/test/test-drivers/package.json index d251fddc2d22..40e7b309c64a 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "A driver abstraction and implementations for testing against server", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-drivers/src/packageVersion.ts b/packages/test/test-drivers/src/packageVersion.ts index f8c1aba8300c..d8efdc1a47f0 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-end-to-end-tests/package.json b/packages/test/test-end-to-end-tests/package.json index 08b8ef9c6d44..5256a75e2210 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { 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 c617ae9a2e8e..8ef82a265f5a 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts b/packages/test/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts index 4f2a963caafe..3b74139a1da1 100644 --- a/packages/test/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts @@ -12,7 +12,7 @@ import { ISummarizer, ISummarizeResults, } from "@fluidframework/container-runtime"; -import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; import { readAndParse } from "@fluidframework/driver-utils"; import { seqFromTree } from "@fluidframework/runtime-utils"; import { @@ -23,7 +23,7 @@ import { createContainerRuntimeFactoryWithDefaultDataStore, } from "@fluidframework/test-utils"; import { describeCompat, getContainerRuntimeApi } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase, IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; +import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; import { MockLogger } from "@fluidframework/telemetry-utils"; import { ISummaryContext } from "@fluidframework/driver-definitions"; import { SharedMatrix } from "@fluidframework/matrix"; @@ -72,8 +72,6 @@ const dataStoreFactory1 = new DataObjectFactory( [], [], ); -const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const registryStoreEntries = new Map>([ [dataStoreFactory1.type, Promise.resolve(dataStoreFactory1)], @@ -87,7 +85,6 @@ const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( { defaultFactory: dataStoreFactory1, registryEntries: registryStoreEntries, - requestHandlers: [innerRequestHandler], runtimeOptions, }, ); @@ -144,7 +141,7 @@ describeCompat( }); function getAndIncrementCellValue( - sharedMatrix: SharedMatrix, + sharedMatrix: SharedMatrix, row: number, column: number, initialValue?: string, diff --git a/packages/test/test-end-to-end-tests/src/test/SummarizerWithOutOfOrderDataStoreRealization.spec.ts b/packages/test/test-end-to-end-tests/src/test/SummarizerWithOutOfOrderDataStoreRealization.spec.ts index 5d2706c9a0d9..b0e06631af30 100644 --- a/packages/test/test-end-to-end-tests/src/test/SummarizerWithOutOfOrderDataStoreRealization.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/SummarizerWithOutOfOrderDataStoreRealization.spec.ts @@ -7,7 +7,7 @@ import { strict as assert } from "assert"; import { DataObject, DataObjectFactory, PureDataObject } from "@fluidframework/aqueduct"; import { IContainer } from "@fluidframework/container-definitions"; import { IContainerRuntimeOptions, ISummarizer } from "@fluidframework/container-runtime"; -import { FluidObject, IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { FluidObject, IFluidHandle } from "@fluidframework/core-interfaces"; import { FluidDataStoreRuntime, mixinSummaryHandler } from "@fluidframework/datastore"; import { SharedMatrix } from "@fluidframework/matrix"; import { SharedMap } from "@fluidframework/map"; @@ -19,7 +19,7 @@ import { createContainerRuntimeFactoryWithDefaultDataStore, } from "@fluidframework/test-utils"; import { describeCompat, getContainerRuntimeApi } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase, IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; +import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; import { pkgVersion } from "../packageVersion.js"; interface ProvideSearchContent { @@ -124,8 +124,6 @@ const dataStoreFactory2 = new DataObjectFactory( [], createDataStoreRuntime(), ); -const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const registryStoreEntries = new Map>([ [dataStoreFactory1.type, Promise.resolve(dataStoreFactory1)], @@ -138,7 +136,6 @@ const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( { defaultFactory: dataStoreFactory1, registryEntries: registryStoreEntries, - requestHandlers: [innerRequestHandler], runtimeOptions, }, ); diff --git a/packages/test/test-end-to-end-tests/src/test/SummarizerWithSearch.spec.ts b/packages/test/test-end-to-end-tests/src/test/SummarizerWithSearch.spec.ts index 291332cd24fb..77c21a84c5cf 100644 --- a/packages/test/test-end-to-end-tests/src/test/SummarizerWithSearch.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/SummarizerWithSearch.spec.ts @@ -21,7 +21,7 @@ import { neverCancelledSummaryToken, SummaryCollection, } from "@fluidframework/container-runtime"; -import { FluidObject, IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { FluidObject, IFluidHandle } from "@fluidframework/core-interfaces"; import { SharedCounter } from "@fluidframework/counter"; import { FluidDataStoreRuntime, mixinSummaryHandler } from "@fluidframework/datastore"; import { DriverHeader, ISummaryContext } from "@fluidframework/driver-definitions"; @@ -31,7 +31,7 @@ import { ISummaryTree, MessageType, } from "@fluidframework/protocol-definitions"; -import { IContainerRuntimeBase, IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; +import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; import { ITestObjectProvider, wrapDocumentServiceFactory, @@ -250,8 +250,6 @@ describeCompat("Prepare for Summary with Search Blobs", "NoCompat", (getTestObje const runtimeOptions: IContainerRuntimeOptions = { gcOptions: { gcAllowed: true }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const registryStoreEntries = new Map>([ [dataStoreFactory1.type, Promise.resolve(dataStoreFactory1)], [dataStoreFactory2.type, Promise.resolve(dataStoreFactory2)], @@ -259,7 +257,6 @@ describeCompat("Prepare for Summary with Search Blobs", "NoCompat", (getTestObje const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory: dataStoreFactory1, registryEntries: registryStoreEntries, - requestHandlers: [innerRequestHandler], runtimeOptions, }); const logger = createChildLogger(); diff --git a/packages/test/test-end-to-end-tests/src/test/agentScheduler.spec.ts b/packages/test/test-end-to-end-tests/src/test/agentScheduler.spec.ts index f289e0b0f82d..e6ab51e331e4 100644 --- a/packages/test/test-end-to-end-tests/src/test/agentScheduler.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/agentScheduler.spec.ts @@ -17,7 +17,6 @@ import { getContainerEntryPointBackCompat, } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; -import { rootDataStoreRequestHandler } from "@fluidframework/request-handler"; // By default, the container loads in read mode. However, pick() attempts silently fail if not in write // mode. To overcome this and test pick(), we can register a fake task (which always tries to perform @@ -36,7 +35,6 @@ describeCompat("AgentScheduler", "FullCompat", (getTestObjectProvider, apis) => AgentSchedulerFactory.type, new AgentSchedulerFactory(), {}, - [rootDataStoreRequestHandler], ), }; diff --git a/packages/test/test-end-to-end-tests/src/test/audience.spec.ts b/packages/test/test-end-to-end-tests/src/test/audience.spec.ts index c01ead18428b..60c15b81119d 100644 --- a/packages/test/test-end-to-end-tests/src/test/audience.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/audience.spec.ts @@ -12,8 +12,6 @@ import { waitForContainerConnection, } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; -import { IRequest } from "@fluidframework/core-interfaces"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { IContainer } from "@fluidframework/container-definitions"; describeCompat("Audience correctness", "FullCompat", (getTestObjectProvider, apis) => { @@ -30,14 +28,11 @@ describeCompat("Audience correctness", "FullCompat", (getTestObjectProvider, api [], [], ); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( apis.containerRuntime.ContainerRuntimeFactoryWithDefaultDataStore, { defaultFactory: dataObjectFactory, registryEntries: [[dataObjectFactory.type, Promise.resolve(dataObjectFactory)]], - requestHandlers: [innerRequestHandler], // Disable summaries so the summarizer client doesn't interfere with the audience runtimeOptions: { summaryOptions: { diff --git a/packages/test/test-end-to-end-tests/src/test/benchmark/PasTable.all.spec.ts b/packages/test/test-end-to-end-tests/src/test/benchmark/PasTable.all.spec.ts index 2e171217e19e..18ca4d43d97d 100644 --- a/packages/test/test-end-to-end-tests/src/test/benchmark/PasTable.all.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/benchmark/PasTable.all.spec.ts @@ -13,7 +13,11 @@ import { SharedString, SharedStringFactory } from "@fluidframework/sequence"; import { benchmarkAll, IBenchmarkParameters } from "./DocumentCreator.js"; function createLocalMatrix(id: string, dataStoreRuntime: MockFluidDataStoreRuntime) { - return new SharedMatrix(dataStoreRuntime, "matrix1", SharedMatrixFactory.Attributes); + return new SharedMatrix( + dataStoreRuntime, + "matrix1", + SharedMatrixFactory.Attributes, + ); } function createString(id: string, dataStoreRuntime: MockFluidDataStoreRuntime) { @@ -21,8 +25,6 @@ function createString(id: string, dataStoreRuntime: MockFluidDataStoreRuntime) { } describeCompat("PAS Test", "NoCompat", () => { - let matrix: SharedMatrix; - let containerRuntimeFactory: MockContainerRuntimeFactory; const dataStoreRuntime = new MockFluidDataStoreRuntime(); const rowSize = 6; const columnSize = 5; @@ -47,10 +49,10 @@ describeCompat("PAS Test", "NoCompat", () => { this.matrix.insertCols(0, columnSize); for (let i = 0; i < rowSize; i++) { for (let j = 0; j < columnSize; j++) { - const id = j.toString() + i.toString(); + const id = `${j},${i}`; const sharedString: SharedString = createString(id, dataStoreRuntime); sharedString.insertText(0, "testValue"); - this.matrix.setCell(i, j, sharedString); + this.matrix.setCell(i, j, sharedString.handle); } } } diff --git a/packages/test/test-end-to-end-tests/src/test/cellEndToEndTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/cellEndToEndTests.spec.ts index 21bdee2c75f1..e7773d2f4289 100644 --- a/packages/test/test-end-to-end-tests/src/test/cellEndToEndTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/cellEndToEndTests.spec.ts @@ -311,7 +311,7 @@ describeCompat("SharedCell orderSequentially", "NoCompat", (getTestObjectProvide let dataObject: ITestFluidObject; let sharedCell: SharedCell; let containerRuntime: ContainerRuntime; - let changedEventData: Serializable[]; + let changedEventData: Serializable[]; const configProvider = (settings: Record): IConfigProviderBase => ({ getRawConfig: (name: string): ConfigTypes => settings[name], diff --git a/packages/test/test-end-to-end-tests/src/test/container.spec.ts b/packages/test/test-end-to-end-tests/src/test/container.spec.ts index 33f2f6670210..1d07b48439d8 100644 --- a/packages/test/test-end-to-end-tests/src/test/container.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/container.spec.ts @@ -9,7 +9,6 @@ import { MockDocumentDeltaConnection } from "@fluid-private/test-loader-utils"; import { IErrorBase, IRequest, IRequestHeader } from "@fluidframework/core-interfaces"; import { ContainerErrorType, - IPendingLocalState, IFluidCodeDetails, IContainer, LoaderHeader, @@ -26,7 +25,6 @@ import { FiveDaysMs, IAnyDriverError, IDocumentServiceFactory, - IResolvedUrl, } from "@fluidframework/driver-definitions"; import { LocalCodeLoader, @@ -46,7 +44,6 @@ import { describeCompat, itExpects, } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { ConfigTypes, DataCorruptionError, @@ -295,12 +292,8 @@ describeCompat("Container", "NoCompat", (getTestObjectProvider) => { }); it("Delta manager receives readonly event when calling container.forceReadonly()", async () => { - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = (_?: unknown) => - new TestContainerRuntimeFactory(TestDataObjectType, getDataStoreFactory(), {}, [ - innerRequestHandler, - ]); + new TestContainerRuntimeFactory(TestDataObjectType, getDataStoreFactory(), {}); const localTestObjectProvider = new TestObjectProvider( Loader, @@ -349,9 +342,9 @@ describeCompat("Container", "NoCompat", (getTestObjectProvider) => { await localTestObjectProvider.makeTestContainer(testContainerConfig); const pendingString = await container.closeAndGetPendingLocalState?.(); assert.ok(pendingString); - const pendingLocalState: IPendingLocalState = JSON.parse(pendingString); + const pendingLocalState: { url?: string } = JSON.parse(pendingString); assert.strictEqual(container.closed, true); - assert.strictEqual(pendingLocalState.url, (container.resolvedUrl as IResolvedUrl).url); + assert.strictEqual(pendingLocalState.url, container.resolvedUrl?.url); }); it("can call connect() and disconnect() on Container", async () => { @@ -385,12 +378,8 @@ describeCompat("Container", "NoCompat", (getTestObjectProvider) => { }); it("can control op processing with connect() and disconnect()", async () => { - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = (_?: unknown) => - new TestContainerRuntimeFactory(TestDataObjectType, getDataStoreFactory(), {}, [ - innerRequestHandler, - ]); + new TestContainerRuntimeFactory(TestDataObjectType, getDataStoreFactory(), {}); const localTestObjectProvider = new TestObjectProvider( Loader, diff --git a/packages/test/test-end-to-end-tests/src/test/createContainerStats.spec.ts b/packages/test/test-end-to-end-tests/src/test/createContainerStats.spec.ts index ed59414a7ed3..55eb788bb0bc 100644 --- a/packages/test/test-end-to-end-tests/src/test/createContainerStats.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/createContainerStats.spec.ts @@ -13,8 +13,6 @@ import { SummaryCollection, ISummaryConfiguration, } from "@fluidframework/container-runtime"; -import { IRequest } from "@fluidframework/core-interfaces"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { MockLogger, createChildLogger } from "@fluidframework/telemetry-utils"; import { ITestObjectProvider, @@ -59,14 +57,11 @@ describeCompat("Generate Summary Stats", "NoCompat", (getTestObjectProvider, api gcAllowed: true, }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( ContainerRuntimeFactoryWithDefaultDataStore, { defaultFactory: dataObjectFactory, registryEntries: [[dataObjectFactory.type, Promise.resolve(dataObjectFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }, ); diff --git a/packages/test/test-end-to-end-tests/src/test/dataStoreRetrieval.spec.ts b/packages/test/test-end-to-end-tests/src/test/dataStoreRetrieval.spec.ts index a6ca0cc3c5b4..9724826fb8f5 100644 --- a/packages/test/test-end-to-end-tests/src/test/dataStoreRetrieval.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/dataStoreRetrieval.spec.ts @@ -4,14 +4,13 @@ */ import { strict as assert } from "assert"; -import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; import { ITestObjectProvider, createContainerRuntimeFactoryWithDefaultDataStore, getContainerEntryPointBackCompat, } from "@fluidframework/test-utils"; import { describeCompat, ITestDataObject } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; /** * These tests retrieve a data store after its creation but at different stages of visibility. @@ -87,8 +86,6 @@ describeCompat( ); let provider: ITestObjectProvider; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); beforeEach(() => { provider = getTestObjectProvider(); @@ -103,7 +100,6 @@ describeCompat( [outerDataObjectFactory.type, Promise.resolve(outerDataObjectFactory)], [innerDataObjectFactory.type, Promise.resolve(innerDataObjectFactory)], ], - requestHandlers: [innerRequestHandler], }, ); const request = provider.driver.createCreateNewRequest(provider.documentId); @@ -129,7 +125,6 @@ describeCompat( [outerDataObjectFactory.type, Promise.resolve(outerDataObjectFactory)], [innerDataObjectFactory.type, Promise.resolve(innerDataObjectFactory)], ], - requestHandlers: [innerRequestHandler], }, ); const request = provider.driver.createCreateNewRequest(provider.documentId); diff --git a/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts b/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts index 645864bbf004..c0e0b2f715c7 100644 --- a/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/deRehydrateContainerTests.spec.ts @@ -154,7 +154,7 @@ describeCompat(`Dehydrate Rehydrate Container Test`, "FullCompat", (getTestObjec function assertBlobContents(subtree: ISnapshotTreeWithBlobContents, key: string): T { const id = subtree.blobs[key]; assert(id, `blob id for ${key} missing`); - const contents = subtree.blobsContents[id]; + const contents = subtree.blobsContents?.[id]; assert(contents, `blob contents for ${key} missing`); return JSON.parse(bufferToString(contents, "utf8")) as T; } @@ -281,7 +281,7 @@ describeCompat(`Dehydrate Rehydrate Container Test`, "FullCompat", (getTestObjec // Check blobs contents for protocolAttributes const protocolAttributesBlobId = snapshotTree.trees[".protocol"].blobs.attributes; assert( - snapshotTree.trees[".protocol"].blobsContents[protocolAttributesBlobId] !== + snapshotTree.trees[".protocol"].blobsContents?.[protocolAttributesBlobId] !== undefined, "Blobs should contain attributes blob", ); diff --git a/packages/test/test-end-to-end-tests/src/test/entryPointCompat.spec.ts b/packages/test/test-end-to-end-tests/src/test/entryPointCompat.spec.ts index 3669ddd0738b..4acc5b87c89b 100644 --- a/packages/test/test-end-to-end-tests/src/test/entryPointCompat.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/entryPointCompat.spec.ts @@ -58,29 +58,6 @@ describe("entryPoint compat", () => { const entryPoint = await container.getEntryPoint?.(); assert.notStrictEqual(entryPoint, undefined, "entryPoint was undefined"); }); - - // TODO: Remove this test when request is removed from Container AB#4991 - it("request pattern", async () => { - const container = await createContainer(); - const requestResult = await container.request({ url: "/" }); - - assert.strictEqual(requestResult.status, 200, requestResult.value); - assert.notStrictEqual(requestResult.value, undefined, "requestResult was undefined"); - }); - - // TODO: Remove this test when request is removed from Container AB#4991 - it("both entryPoint and request pattern", async () => { - const container = await createContainer(); - const entryPoint = await container.getEntryPoint?.(); - const requestResult = await container.request({ url: "/" }); - - assert.strictEqual(requestResult.status, 200, requestResult.value); - assert.strictEqual( - entryPoint, - requestResult.value, - "entryPoint and requestResult expected to be the same", - ); - }); }); const loaderWithRequest = "2.0.0-internal.7.0.0"; @@ -100,7 +77,7 @@ describe("entryPoint compat", () => { it("request pattern works", async () => { const container = await createContainer(); - const requestResult = await container.request({ url: "/" }); + const requestResult = await (container as any).request({ url: "/" }); assert.strictEqual(requestResult.status, 200, requestResult.value); assert.notStrictEqual(requestResult.value, undefined, "requestResult was undefined"); @@ -108,7 +85,7 @@ describe("entryPoint compat", () => { it("request pattern works when entryPoint is available", async () => { const container = await createContainer(); - const requestResult = await container.request({ url: "/" }); + const requestResult = await (container as any).request({ url: "/" }); // Verify request pattern still works for older loaders (even with entryPoint available) assert.strictEqual(requestResult.status, 200, requestResult.value); diff --git a/packages/test/test-end-to-end-tests/src/test/fluidObjectHandle.spec.ts b/packages/test/test-end-to-end-tests/src/test/fluidObjectHandle.spec.ts index 2d903b2161c0..57ad6f0960a6 100644 --- a/packages/test/test-end-to-end-tests/src/test/fluidObjectHandle.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/fluidObjectHandle.spec.ts @@ -17,6 +17,7 @@ import { ITestDataObject, TestDataObjectType, } from "@fluid-private/test-version-utils"; +import { ContainerRuntime } from "@fluidframework/container-runtime"; describeCompat("FluidObjectHandle", "FullCompat", (getTestObjectProvider) => { let provider: ITestObjectProvider; @@ -50,8 +51,9 @@ describeCompat("FluidObjectHandle", "FullCompat", (getTestObjectProvider) => { const absolutePath = ""; // Verify that the local client's ContainerRuntime has the correct absolute path. - const containerRuntime1 = - firstContainerObject1._context.containerRuntime.IFluidHandleContext; + const containerRuntime1 = ( + firstContainerObject1._context.containerRuntime as ContainerRuntime + ).IFluidHandleContext; assert.equal( containerRuntime1.absolutePath, absolutePath, @@ -59,8 +61,9 @@ describeCompat("FluidObjectHandle", "FullCompat", (getTestObjectProvider) => { ); // Verify that the remote client's ContainerRuntime has the correct absolute path. - const containerRuntime2 = - secondContainerObject1._context.containerRuntime.IFluidHandleContext; + const containerRuntime2 = ( + secondContainerObject1._context.containerRuntime as ContainerRuntime + ).IFluidHandleContext; assert.equal( containerRuntime2.absolutePath, absolutePath, diff --git a/packages/test/test-end-to-end-tests/src/test/gc/gcContainerRuntimeCompat.spec.ts b/packages/test/test-end-to-end-tests/src/test/gc/gcContainerRuntimeCompat.spec.ts index 9710b79101a2..9ff1ccb2232a 100644 --- a/packages/test/test-end-to-end-tests/src/test/gc/gcContainerRuntimeCompat.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/gc/gcContainerRuntimeCompat.spec.ts @@ -7,9 +7,7 @@ import { strict as assert } from "assert"; import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct"; import { IContainer } from "@fluidframework/container-definitions"; import { IContainerRuntimeOptions } from "@fluidframework/container-runtime"; -import { IRequest } from "@fluidframework/core-interfaces"; import { ISummaryTree } from "@fluidframework/protocol-definitions"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { ITestFluidObject, ITestObjectProvider, @@ -47,12 +45,9 @@ describeCompat.skip("GC summary compatibility tests", "FullCompat", (getTestObje }, gcOptions: { gcAllowed: true }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory: dataObjectFactory, registryEntries: [[dataObjectFactory.type, Promise.resolve(dataObjectFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }); return provider.createContainer(runtimeFactory, { configProvider: mockConfigProvider() }); diff --git a/packages/test/test-end-to-end-tests/src/test/gc/gcTreeSummaryHandles.spec.ts b/packages/test/test-end-to-end-tests/src/test/gc/gcTreeSummaryHandles.spec.ts index e6ddbc445c67..d70658f43e79 100644 --- a/packages/test/test-end-to-end-tests/src/test/gc/gcTreeSummaryHandles.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/gc/gcTreeSummaryHandles.spec.ts @@ -18,10 +18,9 @@ import { SummarizerStopReason, SummaryCollection, } from "@fluidframework/container-runtime"; -import { IRequest } from "@fluidframework/core-interfaces"; import { DriverHeader, ISummaryContext } from "@fluidframework/driver-definitions"; import { ISummaryTree, SummaryType } from "@fluidframework/protocol-definitions"; -import { gcTreeKey, IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; +import { gcTreeKey } from "@fluidframework/runtime-definitions"; import { ITestFluidObject, ITestObjectProvider, @@ -191,14 +190,11 @@ describeCompat( const runtimeOptions: IContainerRuntimeOptions = { gcOptions: { gcAllowed: true }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( ContainerRuntimeFactoryWithDefaultDataStore, { defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }, ); diff --git a/packages/test/test-end-to-end-tests/src/test/gc/gcVersionUpdate.spec.ts b/packages/test/test-end-to-end-tests/src/test/gc/gcVersionUpdate.spec.ts index 4587ecd912e7..b35a523afe81 100644 --- a/packages/test/test-end-to-end-tests/src/test/gc/gcVersionUpdate.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/gc/gcVersionUpdate.spec.ts @@ -8,11 +8,7 @@ import { IContainer } from "@fluidframework/container-definitions"; import { IContainerRuntimeOptions, ISummarizer } from "@fluidframework/container-runtime"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; import { ISummaryTree, SummaryType } from "@fluidframework/protocol-definitions"; -import { - channelsTreeName, - gcTreeKey, - IContainerRuntimeBase, -} from "@fluidframework/runtime-definitions"; +import { channelsTreeName, gcTreeKey } from "@fluidframework/runtime-definitions"; import { ITestFluidObject, ITestObjectProvider, @@ -23,7 +19,6 @@ import { waitForContainerConnection, } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; -import { IRequest } from "@fluidframework/core-interfaces"; import { IGCMetadata, IGarbageCollector, @@ -55,15 +50,11 @@ describeCompat("GC version update", "NoCompat", (getTestObjectProvider, apis) => gcOptions: { gcAllowed: true }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); - const defaultRuntimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( ContainerRuntimeFactoryWithDefaultDataStore, { defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }, ); diff --git a/packages/test/test-end-to-end-tests/src/test/idCompressor.spec.ts b/packages/test/test-end-to-end-tests/src/test/idCompressor.spec.ts index 47ef537fe10f..d7747f00bbbf 100644 --- a/packages/test/test-end-to-end-tests/src/test/idCompressor.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/idCompressor.spec.ts @@ -716,8 +716,8 @@ describeCompat("IdCompressor Summaries", "NoCompat", (getTestObjectProvider) => const base64Content = (compressorSummary as any).content as string; const floatView = new Float64Array(stringToBuffer(base64Content, "base64")); return { - sessionCount: floatView[3], - clusterCount: floatView[4], + sessionCount: floatView[2], + clusterCount: floatView[3], }; } diff --git a/packages/test/test-end-to-end-tests/src/test/incrementalSummaryContext.spec.ts b/packages/test/test-end-to-end-tests/src/test/incrementalSummaryContext.spec.ts index 1c0af749d9af..4dfb25f2a091 100644 --- a/packages/test/test-end-to-end-tests/src/test/incrementalSummaryContext.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/incrementalSummaryContext.spec.ts @@ -7,9 +7,7 @@ import { strict as assert } from "assert"; import { ContainerRuntimeFactoryWithDefaultDataStore } from "@fluidframework/aqueduct"; import { IContainer, LoaderHeader } from "@fluidframework/container-definitions"; import { IContainerRuntimeOptions } from "@fluidframework/container-runtime"; -import { IRequest } from "@fluidframework/core-interfaces"; import { - IContainerRuntimeBase, IExperimentalIncrementalSummaryContext, ISummaryTreeWithStats, ITelemetryContext, @@ -474,12 +472,9 @@ describeCompat( const runtimeOptions: IContainerRuntimeOptions = { summaryOptions: { summaryConfigOverrides: { state: "disabled" } }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }); diff --git a/packages/test/test-end-to-end-tests/src/test/legacyChunking.spec.ts b/packages/test/test-end-to-end-tests/src/test/legacyChunking.spec.ts index 7f717b73f036..929e19cd570b 100644 --- a/packages/test/test-end-to-end-tests/src/test/legacyChunking.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/legacyChunking.spec.ts @@ -19,8 +19,7 @@ import { getDataRuntimeApi, } from "@fluid-private/test-version-utils"; import { IContainer } from "@fluidframework/container-definitions"; -import { FlushMode, IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; -import { IRequest } from "@fluidframework/core-interfaces"; +import { FlushMode } from "@fluidframework/runtime-definitions"; const versionWithChunking = "0.56.0"; @@ -38,8 +37,6 @@ describeInstallVersions( }); afterEach(async () => provider.reset()); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const mapId = "map"; const registry: ChannelFactoryRegistry = [[mapId, SharedMap.getFactory()]]; const testContainerConfig: ITestContainerConfig = { @@ -63,7 +60,6 @@ describeInstallVersions( oldDataObjectFactory, [[oldDataObjectFactory.type, Promise.resolve(oldDataObjectFactory)]], undefined, - [innerRequestHandler], { // Chunking did not work with FlushMode.TurnBased, // as it was breaking batching semantics. So we need diff --git a/packages/test/test-end-to-end-tests/src/test/loadModes.spec.ts b/packages/test/test-end-to-end-tests/src/test/loadModes.spec.ts index c244e3606327..e95f79255f33 100644 --- a/packages/test/test-end-to-end-tests/src/test/loadModes.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/loadModes.spec.ts @@ -24,14 +24,10 @@ import { DataObjectFactoryType, ITestContainerConfig, ITestFluidObject, + createSummarizerFromFactory, } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; import { IResolvedUrl } from "@fluidframework/driver-definitions"; -import { - ContainerRuntime, - ISummarizer, - TEST_requestSummarizer, -} from "@fluidframework/container-runtime"; import { SharedMap } from "@fluidframework/map"; const counterKey = "count"; @@ -164,34 +160,6 @@ describeCompat("LoadModes", "NoCompat", (getTestObjectProvider) => { }); } - async function createSummarizerFromContainer(container: IContainer): Promise { - const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ - defaultFactory: testDataObjectFactory, - registryEntries: [[testDataObjectFactory.type, Promise.resolve(testDataObjectFactory)]], - }); - const loader = createLoader( - [[provider.defaultCodeDetails, runtimeFactory]], - provider.documentServiceFactory, - provider.urlResolver, - provider.logger, - ); - loaderContainerTracker.add(loader); - const absoluteUrl = await container.getAbsoluteUrl(""); - if (absoluteUrl === undefined) { - throw new Error("URL could not be resolved"); - } - const summarizer = await TEST_requestSummarizer(loader, absoluteUrl); - await waitForSummarizerConnection(summarizer); - return summarizer; - } - - async function waitForSummarizerConnection(summarizer: ISummarizer): Promise { - const runtime = (summarizer as any).runtime as ContainerRuntime; - if (!runtime.connected) { - return new Promise((resolve) => runtime.once("connected", () => resolve())); - } - } - it("Can load a paused container", async () => { const headers: IRequestHeader = { [LoaderHeader.loadMode]: { @@ -296,7 +264,11 @@ describeCompat("LoadModes", "NoCompat", (getTestObjectProvider) => { }); it("Can load a paused container after a summary", async () => { - const summarizer = await createSummarizerFromContainer(container1); + const { summarizer } = await createSummarizerFromFactory( + provider, + container1, + testDataObjectFactory, + ); // Send 5 ops const numIncrement = 5; for (let i = 0; i < numIncrement; i++) { @@ -435,7 +407,11 @@ describeCompat("LoadModes", "NoCompat", (getTestObjectProvider) => { }); it("Throw if attempting to pause at a sequence number before the latest summary", async () => { - const summarizer = await createSummarizerFromContainer(container1); + const { summarizer } = await createSummarizerFromFactory( + provider, + container1, + testDataObjectFactory, + ); // Send 5 ops const numIncrement = 5; for (let i = 0; i < numIncrement; i++) { diff --git a/packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts b/packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts deleted file mode 100644 index e6845867efc3..000000000000 --- a/packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts +++ /dev/null @@ -1,330 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { strict as assert } from "assert"; -import { parse } from "url"; -import { IContainer, IHostLoader, LoaderHeader } from "@fluidframework/container-definitions"; - -import { IRequest, IResponse, IRequestHeader } from "@fluidframework/core-interfaces"; -import { createAndAttachContainer, ITestObjectProvider } from "@fluidframework/test-utils"; -import { describeCompat, itSkipsFailureOnSpecificDrivers } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; -import { RuntimeHeaders } from "@fluidframework/container-runtime"; -import { requestResolvedObjectFromContainer } from "@fluidframework/container-loader"; -import { requestFluidObject } from "@fluidframework/runtime-utils"; - -// REVIEW: enable compat testing? -// TODO: Remove this file when request is removed from Loader AB#4991 -describeCompat("Loader.request", "NoCompat", (getTestObjectProvider, apis) => { - const { - dataRuntime: { DataObject, DataObjectFactory }, - containerRuntime: { ContainerRuntimeFactoryWithDefaultDataStore }, - } = apis; - class TestSharedDataObject1 extends DataObject { - public get _root() { - return this.root; - } - - public get _runtime() { - return this.runtime; - } - - public get _context() { - return this.context; - } - - // Returns query params (if any) in the request. - // Used in tests that verify query params work correctly with loader.request - public async request(request: IRequest): Promise { - const url = request.url; - const parsed = parse(url, true); - if (parsed.query.inspect === "1") { - // returning query params instead of the data object for testing purposes - return { mimeType: "text/plain", status: 200, value: `${parsed.search}` }; - } else if (parsed?.pathname === "/") { - return { value: this, status: 200, mimeType: "fluid/object" }; - } else { - return super.request(request); - } - } - } - - class TestSharedDataObject2 extends DataObject { - public get _root() { - return this.root; - } - - public get _runtime() { - return this.runtime; - } - - public get _context() { - return this.context; - } - - public get _id() { - return this.id; - } - - public async request(request: IRequest): Promise { - const url = request.url; - const parsed = parse(url, true); - return parsed?.pathname === "/" - ? { value: this, status: 200, mimeType: "fluid/object" } - : super.request(request); - } - } - - /** - * Data object that handles requests that have headers. It returns the headers in the request. This is - * used to validate that headers are correctly propagated all the way in the stack. - */ - class TestDataObjectWithRequestHeaders extends DataObject { - public async request(request: IRequest): Promise { - // If the request has headers, return a specialized response with the headers in the request. - return request.headers !== undefined - ? { value: request.headers, status: 200, mimeType: "request/headers" } - : super.request(request); - } - } - - const testSharedDataObjectFactory1 = new DataObjectFactory( - "TestSharedDataObject1", - TestSharedDataObject1, - [], - [], - ); - - const testSharedDataObjectFactory2 = new DataObjectFactory( - "TestSharedDataObject2", - TestSharedDataObject2, - [], - [], - ); - - const testFactoryWithRequestHeaders = new DataObjectFactory( - "TestDataObjectWithRequestHeaders", - TestDataObjectWithRequestHeaders, - [], - [], - ); - - let provider: ITestObjectProvider; - - let dataStore1: TestSharedDataObject1; - let dataStore2: TestSharedDataObject2; - let loader: IHostLoader; - let container: IContainer; - - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); - - const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ - defaultFactory: testSharedDataObjectFactory1, - registryEntries: [ - [testSharedDataObjectFactory1.type, Promise.resolve(testSharedDataObjectFactory1)], - [testSharedDataObjectFactory2.type, Promise.resolve(testSharedDataObjectFactory2)], - [testFactoryWithRequestHeaders.type, Promise.resolve(testFactoryWithRequestHeaders)], - ], - requestHandlers: [innerRequestHandler], - }); - - beforeEach(async () => { - provider = getTestObjectProvider(); - loader = provider.createLoader([[provider.defaultCodeDetails, runtimeFactory]]); - container = await createAndAttachContainer( - provider.defaultCodeDetails, - loader, - provider.driver.createCreateNewRequest(provider.documentId), - ); - dataStore1 = (await container.getEntryPoint()) as TestSharedDataObject1; - - dataStore2 = await testSharedDataObjectFactory2.createInstance( - dataStore1._context.containerRuntime, - ); - - // this binds dataStore2 to dataStore1 - dataStore1._root.set("key", dataStore2.handle); - - await provider.ensureSynchronized(); - }); - - it("can create the data objects with correct types", async () => { - const testUrl1 = await container.getAbsoluteUrl(dataStore1.handle.absolutePath); - assert(testUrl1, "dataStore1 url is undefined"); - const testDataStore1 = await requestFluidObject(loader, testUrl1); - const testUrl2 = await container.getAbsoluteUrl(dataStore2.handle.absolutePath); - assert(testUrl2, "dataStore2 url is undefined"); - const testDataStore2 = await requestFluidObject(loader, testUrl2); - - assert( - testDataStore1 instanceof TestSharedDataObject1, - "requestFromLoader returns the wrong type for default", - ); - assert( - testDataStore2 instanceof TestSharedDataObject2, - "requestFromLoader returns the wrong type for object2", - ); - }); - - it("can create data object using url with second id, having correct type and id", async () => { - const dataStore2Url = await container.getAbsoluteUrl(dataStore2.handle.absolutePath); - assert(dataStore2Url, "dataStore2 url is undefined"); - const testDataStore = await requestFluidObject(loader, dataStore2Url); - - assert( - testDataStore instanceof TestSharedDataObject2, - "request returns the wrong type with long url", - ); - assert.equal(testDataStore.id, dataStore2.id, "id is not correct"); - }); - - it("can create data object using url with second id, having distinct value from default", async () => { - const containerUrl = await container.getAbsoluteUrl(""); - assert(containerUrl, "url is undefined"); - const container2 = await loader.resolve({ url: containerUrl }); - container2.connect(); - - const testDataStore = await requestFluidObject( - container2, - dataStore2.handle.absolutePath, - ); - - dataStore1._root.set("color", "purple"); - dataStore2._root.set("color", "pink"); - - await provider.ensureSynchronized(); - - assert.equal(dataStore1._root.get("color"), "purple", "datastore1 value incorrect"); - assert.equal( - testDataStore._root.get("color"), - dataStore2._root.get("color"), - "two instances of same dataStore have different values", - ); - }); - - itSkipsFailureOnSpecificDrivers( - "loaded container is paused using loader pause flags", - ["odsp"], - async () => { - // load the container paused - const headers: IRequestHeader = { - [LoaderHeader.loadMode]: { deltaConnection: "delayed" }, - }; - const url = await container.getAbsoluteUrl(""); - assert(url, "url is undefined"); - const container2 = await loader.resolve({ url, headers }); - - // create a new data store using the original container - const newDataStore = await testSharedDataObjectFactory2.createInstance( - dataStore1._context.containerRuntime, - ); - // this binds newDataStore to dataStore1 - dataStore1._root.set("key", newDataStore.handle); - - // the dataStore3 shouldn't exist in container2 yet (because the loader isn't caching the container) - let success = true; - try { - await requestFluidObject(container2, { - url: newDataStore.id, - headers: { wait: false }, // data store load default wait to true currently - }); - success = false; - } catch (e) {} - assert(success, "Loader pause flags doesn't pause container op processing"); - - container2.connect(); - - // Flush all the ops - await provider.ensureSynchronized(); - - const newDataStore2 = await requestFluidObject(container2, { url: newDataStore.id }); - assert( - newDataStore2 instanceof TestSharedDataObject2, - "requestFromLoader returns the wrong type for object2", - ); - }, - ); - - // TODO: Remove this test when request is removed from Container AB#4991 - itSkipsFailureOnSpecificDrivers("can handle url with query params", ["odsp"], async () => { - const url = await container.getAbsoluteUrl(""); - assert(url, "url is undefined"); - const testUrl = `${url}${ - url.includes("?") ? "&query1=1&query2=2&inspect=1" : "?query1=1&query2=2&inspect=1" - }`; - - const response = await loader.request({ url: testUrl }); - const searchParams = new URLSearchParams(response.value); - assert.strictEqual( - searchParams.get("query1"), - "1", - "request did not pass the right query to the data store", - ); - assert.strictEqual( - searchParams.get("query2"), - "2", - "request did not pass the right query to the data store", - ); - }); - - it("requestResolvedObjectFromContainer can handle url with query params", async () => { - const url = await container.getAbsoluteUrl(""); - assert(url, "url is undefined"); - const testUrl = `${url}${ - url.includes("?") ? "&query1=1&query2=2&inspect=1" : "?query1=1&query2=2&inspect=1" - }`; - - const newLoader = provider.createLoader([[provider.defaultCodeDetails, runtimeFactory]]); - const resolvedContainer = await newLoader.resolve({ url: testUrl }); - const response = await requestResolvedObjectFromContainer(resolvedContainer); - const searchParams = new URLSearchParams(response.value); - assert.strictEqual( - searchParams.get("query1"), - "1", - "request did not pass the right query to the data store", - ); - assert.strictEqual( - searchParams.get("query2"), - "2", - "request did not pass the right query to the data store", - ); - }); - - // TODO: Remove this test when request is removed from Container AB#4991 - it("can handle requests with headers", async () => { - const containerUrl = await container.getAbsoluteUrl(""); - assert(containerUrl, "url is undefined"); - const container2 = await loader.resolve({ url: containerUrl }); - container2.connect(); - - const dataStoreWithRequestHeaders = await testFactoryWithRequestHeaders.createInstance( - dataStore1._context.containerRuntime, - ); - dataStore1._root.set("key", dataStoreWithRequestHeaders.handle); - - // Flush all the ops - await provider.ensureSynchronized(); - - const headers = { - wait: false, - [RuntimeHeaders.viaHandle]: true, - }; - // Request to the newly created data store with headers. - const response = await container2.request({ url: dataStoreWithRequestHeaders.id, headers }); - - assert.strictEqual(response.status, 200, "Did not return the correct status"); - assert.strictEqual( - response.mimeType, - "request/headers", - "Did not get the correct mimeType", - ); - assert.deepStrictEqual( - response.value, - headers, - "Did not get the correct headers in the response", - ); - }); -}); diff --git a/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts b/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts index 337d24aeae7e..2ee7f60b3e90 100644 --- a/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts @@ -11,10 +11,10 @@ import { IDataObjectProps, } from "@fluidframework/aqueduct"; import { IContainer, IFluidCodeDetails } from "@fluidframework/container-definitions"; -import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; import { SharedCounter } from "@fluidframework/counter"; import { IFluidDataStoreRuntime } from "@fluidframework/datastore-definitions"; -import { IContainerRuntimeBase, IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; +import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; import { SharedString } from "@fluidframework/sequence"; import { createAndAttachContainer, @@ -115,12 +115,9 @@ describeCompat("LocalLoader", "NoCompat", (getTestObjectProvider) => { documentId: string, defaultFactory: IFluidDataStoreFactory, ): Promise { - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [innerRequestHandler], }); const loader = createLoader( [[codeDetails, runtimeFactory]], @@ -142,12 +139,9 @@ describeCompat("LocalLoader", "NoCompat", (getTestObjectProvider) => { containerUrl: IResolvedUrl | undefined, defaultFactory: IFluidDataStoreFactory, ): Promise { - const inner = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory, registryEntries: [[defaultFactory.type, Promise.resolve(defaultFactory)]], - requestHandlers: [inner], }); const loader = createLoader( [[codeDetails, runtimeFactory]], 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 269bb066761f..ffcdd856854e 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 @@ -19,6 +19,7 @@ import { ContainerRuntime } from "@fluidframework/container-runtime"; import { IValueChanged, SharedDirectory, SharedMap } from "@fluidframework/map"; import { SharedCell } from "@fluidframework/cell"; import { ConfigTypes, IConfigProviderBase } from "@fluidframework/core-interfaces"; +import { Serializable } from "@fluidframework/datastore-definitions"; const stringId = "sharedStringKey"; const string2Id = "sharedString2Key"; @@ -50,7 +51,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi let sharedDir: SharedDirectory; let sharedCell: SharedCell; let sharedMap: SharedMap; - let changedEventData: IValueChanged[]; + let changedEventData: (IValueChanged | Serializable)[]; let containerRuntime: ContainerRuntime; let error: Error | undefined; @@ -128,8 +129,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.equal(changedEventData[1].key, "key1"); - assert.equal(changedEventData[1].previousValue, undefined); + assert.deepEqual(changedEventData[1], { key: "key1", previousValue: undefined }); assert.equal(changedEventData[2], 2); @@ -138,16 +138,14 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[3]}`, ); - assert.equal(changedEventData[4].key, "key1"); - assert.equal(changedEventData[4].previousValue, 0); + assert.deepEqual(changedEventData[4], { key: "key1", previousValue: 0 }); assert.equal(changedEventData[5], undefined); // rollback assert.equal(changedEventData[6], 2); - assert.equal(changedEventData[7].key, "key1"); - assert.equal(changedEventData[7].previousValue, undefined); + assert.deepEqual(changedEventData[7], { key: "key1", previousValue: undefined }); assert( changedEventData[8] instanceof SequenceDeltaEvent, @@ -197,8 +195,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.equal(changedEventData[1].key, "key1"); - assert.equal(changedEventData[1].previousValue, undefined); + assert.deepEqual(changedEventData[1], { key: "key1", previousValue: undefined }); assert( changedEventData[2] instanceof SequenceDeltaEvent, @@ -207,8 +204,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi assert.equal(changedEventData[3], 2); - assert.equal(changedEventData[4].key, "key1"); - assert.equal(changedEventData[4].previousValue, 0); + assert.deepEqual(changedEventData[4], { key: "key1", previousValue: 0 }); assert( changedEventData[5] instanceof SequenceDeltaEvent, @@ -252,8 +248,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[15]}`, ); - assert.equal(changedEventData[16].key, "key1"); - assert.equal(changedEventData[16].previousValue, 3); + assert.deepEqual(changedEventData[16], { key: "key1", previousValue: 3 }); }); it("Should handle rollback on multiple instances of the same DDS type", () => { @@ -288,16 +283,14 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.equal(changedEventData[1].key, "key"); - assert.equal(changedEventData[1].previousValue, undefined); + assert.deepEqual(changedEventData[1], { key: "key", previousValue: undefined }); assert( changedEventData[2] instanceof SequenceDeltaEvent, `Unexpected event type - ${typeof changedEventData[2]}`, ); - assert.equal(changedEventData[3].key, "key"); - assert.equal(changedEventData[3].previousValue, 1); + assert.deepEqual(changedEventData[3], { key: "key", previousValue: 1 }); assert( changedEventData[4] instanceof SequenceDeltaEvent, @@ -320,8 +313,7 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[7]}`, ); - assert.equal(changedEventData[8].key, "key"); - assert.equal(changedEventData[8].previousValue, undefined); + assert.deepEqual(changedEventData[8], { key: "key", previousValue: undefined }); }); it("Should handle nested calls to orderSequentially", () => { @@ -361,11 +353,9 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[0]}`, ); - assert.equal(changedEventData[1].key, "key"); - assert.equal(changedEventData[1].previousValue, undefined); + assert.deepEqual(changedEventData[1], { key: "key", previousValue: undefined }); - assert.equal(changedEventData[2].key, "key"); - assert.equal(changedEventData[2].previousValue, 1); + assert.deepEqual(changedEventData[2], { key: "key", previousValue: 1 }); assert( changedEventData[3] instanceof SequenceDeltaEvent, @@ -378,14 +368,11 @@ describeCompat("Multiple DDS orderSequentially", "NoCompat", (getTestObjectProvi `Unexpected event type - ${typeof changedEventData[4]}`, ); - assert.equal(changedEventData[5].key, "key"); - assert.equal(changedEventData[5].previousValue, 0); + assert.deepEqual(changedEventData[5], { key: "key", previousValue: 0 }); // rollback - outer orderSequentially call - assert.equal(changedEventData[6].key, "key"); - assert.equal(changedEventData[6].previousValue, undefined); + assert.deepEqual(changedEventData[6], { key: "key", previousValue: undefined }); - assert.equal(changedEventData[7].key, "key"); - assert.equal(changedEventData[7].previousValue, 0); + assert.deepEqual(changedEventData[7], { key: "key", previousValue: 0 }); }); }); diff --git a/packages/test/test-end-to-end-tests/src/test/rootDatastores.spec.ts b/packages/test/test-end-to-end-tests/src/test/rootDatastores.spec.ts index 4fc2d435469e..e188a3ea9601 100644 --- a/packages/test/test-end-to-end-tests/src/test/rootDatastores.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/rootDatastores.spec.ts @@ -13,7 +13,6 @@ import { DefaultSummaryConfiguration, } from "@fluidframework/container-runtime"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import { IFluidRouter } from "@fluidframework/core-interfaces"; import { ConfigTypes, IConfigProviderBase, @@ -27,6 +26,7 @@ import { getContainerEntryPointBackCompat, } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; +import { IDataStore } from "@fluidframework/runtime-definitions"; describeCompat("Named root data stores", "FullCompat", (getTestObjectProvider) => { let provider: ITestObjectProvider; @@ -237,7 +237,7 @@ describeCompat("Named root data stores", "FullCompat", (getTestObjectProvider) = if (provider.type === "TestObjectProviderWithVersionedLoad") { this.skip(); } - const datastores: IFluidRouter[] = []; + const datastores: IDataStore[] = []; const createAliasedDataStore = async () => { try { await getAliasedDataStoreEntryPoint(dataObject1, alias); diff --git a/packages/test/test-end-to-end-tests/src/test/summaries.spec.ts b/packages/test/test-end-to-end-tests/src/test/summaries.spec.ts index 76706edf503f..dfd4a9b31e14 100644 --- a/packages/test/test-end-to-end-tests/src/test/summaries.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/summaries.spec.ts @@ -14,7 +14,6 @@ import { ISummaryRuntimeOptions, DefaultSummaryConfiguration, SummaryCollection, - TEST_requestSummarizer, } from "@fluidframework/container-runtime"; import { ISummaryContext } from "@fluidframework/driver-definitions"; import { ISummaryBlob, ISummaryTree, SummaryType } from "@fluidframework/protocol-definitions"; @@ -83,36 +82,17 @@ async function createMainContainerAndSummarizer( if (absoluteUrl === undefined) { throw new Error("URL could not be resolved"); } - const summarizer = await TEST_requestSummarizer(loader, absoluteUrl); - await waitForSummarizerConnection(summarizer); + const { summarizer } = await createSummarizer( + provider, + container, + containerConfig ?? testContainerConfig, + ); return { mainContainer: container, summarizer, }; } -async function createSummarizerFromContainer( - provider: ITestObjectProvider, - container: IContainer, - containerConfig?: ITestContainerConfig, -): Promise { - const loader = provider.makeTestLoader(containerConfig ?? testContainerConfig); - const absoluteUrl = await container.getAbsoluteUrl(""); - if (absoluteUrl === undefined) { - throw new Error("URL could not be resolved"); - } - const summarizer = await TEST_requestSummarizer(loader, absoluteUrl); - await waitForSummarizerConnection(summarizer); - return summarizer; -} - -async function waitForSummarizerConnection(summarizer: ISummarizer): Promise { - const runtime = (summarizer as any).runtime as ContainerRuntime; - if (!runtime.connected) { - return new Promise((resolve) => runtime.once("connected", () => resolve())); - } -} - function readBlobContent(content: ISummaryBlob["content"]): unknown { const json = typeof content === "string" ? content : bufferToString(content, "utf8"); return JSON.parse(json); @@ -559,7 +539,11 @@ describeCompat("SingleCommit Summaries Tests", "NoCompat", (getTestObjectProvide await flushPromises(); // Create new summarizer - const summarizer2 = await createSummarizerFromContainer(provider, mainContainer); + const { summarizer: summarizer2 } = await createSummarizer( + provider, + mainContainer, + testContainerConfig, + ); // Second summary should be discarded const containerRuntime = (summarizer2 as any).runtime as ContainerRuntime; @@ -578,7 +562,11 @@ describeCompat("SingleCommit Summaries Tests", "NoCompat", (getTestObjectProvide await flushPromises(); // Create new summarizer - const summarizer3 = await createSummarizerFromContainer(provider, mainContainer); + const { summarizer: summarizer3 } = await createSummarizer( + provider, + mainContainer, + testContainerConfig, + ); // Summarize third time const result3: ISummarizeResults = summarizer3.summarizeOnDemand({ reason: "test3" }); @@ -630,7 +618,7 @@ describeCompat("SingleCommit Summaries Tests", "NoCompat", (getTestObjectProvide ); summarizer.close(); - const summarizer2 = await createSummarizerFromContainer( + const { summarizer: summarizer2 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, @@ -676,7 +664,7 @@ describeCompat("SingleCommit Summaries Tests", "NoCompat", (getTestObjectProvide await flushPromises(); // Create new summarizer - const summarizer2 = await createSummarizerFromContainer( + const { summarizer: summarizer2 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, @@ -701,7 +689,7 @@ describeCompat("SingleCommit Summaries Tests", "NoCompat", (getTestObjectProvide await flushPromises(); // Create new summarizer - const summarizer3 = await createSummarizerFromContainer( + const { summarizer: summarizer3 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, diff --git a/packages/test/test-end-to-end-tests/src/test/summaryDataStoreStats.spec.ts b/packages/test/test-end-to-end-tests/src/test/summaryDataStoreStats.spec.ts index 9968378f2f45..9a62d3e01b9a 100644 --- a/packages/test/test-end-to-end-tests/src/test/summaryDataStoreStats.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/summaryDataStoreStats.spec.ts @@ -16,8 +16,7 @@ import { ISummaryConfiguration, DefaultSummaryConfiguration, } from "@fluidframework/container-runtime"; -import { ITelemetryBaseEvent, IRequest } from "@fluidframework/core-interfaces"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; +import { ITelemetryBaseEvent } from "@fluidframework/core-interfaces"; import { MockLogger, createChildLogger } from "@fluidframework/telemetry-utils"; import { ITestObjectProvider, timeoutAwait } from "@fluidframework/test-utils"; import { describeCompat } from "@fluid-private/test-version-utils"; @@ -58,12 +57,9 @@ describeCompat("Generate Summary Stats", "NoCompat", (getTestObjectProvider) => gcAllowed: true, }, }; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory: dataObjectFactory, registryEntries: [[dataObjectFactory.type, Promise.resolve(dataObjectFactory)]], - requestHandlers: [innerRequestHandler], runtimeOptions, }); diff --git a/packages/test/test-pairwise-generator/package.json b/packages/test/test-pairwise-generator/package.json index f1c273bc7c6a..c97fc7f764f4 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-service-load/package.json b/packages/test/test-service-load/package.json index 63e974c0a782..2d2a5ed108c0 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Service load tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-service-load/src/loadTestDataStore.ts b/packages/test/test-service-load/src/loadTestDataStore.ts index 18db8b298b8a..5c617bfbebb7 100644 --- a/packages/test/test-service-load/src/loadTestDataStore.ts +++ b/packages/test/test-service-load/src/loadTestDataStore.ts @@ -10,7 +10,7 @@ import { DataObject, DataObjectFactory, } from "@fluidframework/aqueduct"; -import { IFluidHandle, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; import { ISharedCounter, SharedCounter } from "@fluidframework/counter"; import { ITaskManager, TaskManager } from "@fluidframework/task-manager"; import { IDirectory, ISharedDirectory, ISharedMap, SharedMap } from "@fluidframework/map"; @@ -748,9 +748,6 @@ const LoadTestDataStoreInstantiationFactory = new DataObjectFactory( {}, ); -const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); - export const createFluidExport = (runtimeOptions: IContainerRuntimeOptions) => new ContainerRuntimeFactoryWithDefaultDataStore({ defaultFactory: LoadTestDataStoreInstantiationFactory, @@ -760,6 +757,5 @@ export const createFluidExport = (runtimeOptions: IContainerRuntimeOptions) => Promise.resolve(LoadTestDataStoreInstantiationFactory), ], ]), - requestHandlers: [innerRequestHandler], runtimeOptions, }); diff --git a/packages/test/test-service-load/src/packageVersion.ts b/packages/test/test-service-load/src/packageVersion.ts index 6d37fc7bc261..d53246d3428d 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-utils/README.md b/packages/test/test-utils/README.md index 4b56ef8d13a7..95ff3698529d 100644 --- a/packages/test/test-utils/README.md +++ b/packages/test/test-utils/README.md @@ -145,10 +145,10 @@ The typical usage for testing a Fluid object is as follows: > We used the same `IFluidCodeDetails` that was used to create the `Loader` in step 3. -6. Get the `Fluid object (TestFluidObject)` by using `requestFluidObject` API in `@fluidframework/runtime-utils`. Then get the `DDS` to test: +6. Get the `Fluid object (TestFluidObject)` by using `getEntryPoint()` API on `IContainer`. Then get the `DDS` to test: ```typescript - const fluidObject = await requestFluidObject(container, "default"); // "default" represent the default Fluid object. + const fluidObject = await container.getEntryPoint(); const sharedString = await fluidObject.getSharedObject("sharedString"); ``` @@ -163,7 +163,7 @@ The typical usage for testing a Fluid object is as follows: urlResolver, ); const container2 = await loader2.resolver({ url: documentUrl }); - const fluidObject = await requestFluidObject(container2, "default"); + const fluidObject = await container2.getEntryPoint(); const sharedString2 = await fluidObject2.getSharedObject("sharedString"); ``` > It is important to use the same `ILocalDeltaConnectionServer` to create the `Loader` and the same `documentId` to load the `Container`. This will make sure that we load the `Container` that was created earlier and do not create a new one. diff --git a/packages/test/test-utils/api-report/test-utils.api.md b/packages/test/test-utils/api-report/test-utils.api.md index 8f439254a193..b24c8a7495b8 100644 --- a/packages/test/test-utils/api-report/test-utils.api.md +++ b/packages/test/test-utils/api-report/test-utils.api.md @@ -278,13 +278,11 @@ export class TestFluidObject implements ITestFluidObject { get handle(): IFluidHandle; // (undocumented) get IFluidLoadable(): this; - // @deprecated (undocumented) - get IFluidRouter(): this; // (undocumented) initialize(existing: boolean): Promise; // (undocumented) get ITestFluidObject(): this; - // @deprecated (undocumented) + // (undocumented) request(request: IRequest): Promise; // (undocumented) root: ISharedMap; diff --git a/packages/test/test-utils/package.json b/packages/test/test-utils/package.json index 58f930a6639a..6e067c2e03e0 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Utilities for Fluid tests", "homepage": "https://fluidframework.com", "repository": { @@ -128,6 +128,9 @@ }, "typeValidation": { "broken": { + "ClassDeclaration_TestFluidObject": { + "backCompat": false + }, "InterfaceDeclaration_ITestObjectProvider": { "backCompat": false, "forwardCompat": false diff --git a/packages/test/test-utils/src/TestSummaryUtils.ts b/packages/test/test-utils/src/TestSummaryUtils.ts index f6b71dd69814..ffda1ed310dc 100644 --- a/packages/test/test-utils/src/TestSummaryUtils.ts +++ b/packages/test/test-utils/src/TestSummaryUtils.ts @@ -19,7 +19,6 @@ import { } from "@fluidframework/core-interfaces"; import { DriverHeader } from "@fluidframework/driver-definitions"; import { - IContainerRuntimeBase, IFluidDataStoreFactory, NamedFluidDataStoreRegistryEntries, } from "@fluidframework/runtime-definitions"; @@ -93,8 +92,6 @@ export async function createSummarizerFromFactory( logger?: ITelemetryBaseLogger, configProvider: IConfigProviderBase = mockConfigProvider(), ): Promise<{ container: IContainer; summarizer: ISummarizer }> { - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const runtimeFactory = createContainerRuntimeFactoryWithDefaultDataStore( containerRuntimeFactoryType, { @@ -102,7 +99,6 @@ export async function createSummarizerFromFactory( registryEntries: registryEntries ?? [ [dataStoreFactory.type, Promise.resolve(dataStoreFactory)], ], - requestHandlers: [innerRequestHandler], runtimeOptions: { summaryOptions: defaultSummaryOptions }, }, ); diff --git a/packages/test/test-utils/src/localCodeLoader.ts b/packages/test/test-utils/src/localCodeLoader.ts index fd56ce2971e6..edbbb8488ee4 100644 --- a/packages/test/test-utils/src/localCodeLoader.ts +++ b/packages/test/test-utils/src/localCodeLoader.ts @@ -13,9 +13,7 @@ import { ICodeDetailsLoader, IFluidModuleWithDetails, } from "@fluidframework/container-definitions"; -import { IRequest } from "@fluidframework/core-interfaces"; import { - IContainerRuntimeBase, IProvideFluidDataStoreFactory, IProvideFluidDataStoreRegistry, } from "@fluidframework/runtime-definitions"; @@ -71,10 +69,6 @@ export class LocalCodeLoader implements ICodeDetailsLoader { "default", maybeExport.IFluidDataStoreFactory, ); - const innerRequestHandler = async ( - request: IRequest, - runtime: IContainerRuntimeBase, - ) => runtime.IFluidHandleContext.resolveHandle(request); fluidModule = { fluidExport: { ...maybeExport, @@ -83,7 +77,6 @@ export class LocalCodeLoader implements ICodeDetailsLoader { registryEntries: [ [defaultFactory.type, Promise.resolve(defaultFactory)], ], - requestHandlers: [innerRequestHandler], runtimeOptions, }), }, diff --git a/packages/test/test-utils/src/packageVersion.ts b/packages/test/test-utils/src/packageVersion.ts index 474adf138c5e..3f77ed281a69 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.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 7c42897b8159..b32440a7460f 100644 --- a/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts +++ b/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts @@ -333,6 +333,7 @@ declare function get_current_ClassDeclaration_TestFluidObject(): declare function use_old_ClassDeclaration_TestFluidObject( use: TypeOnly): void; use_old_ClassDeclaration_TestFluidObject( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_TestFluidObject()); /* diff --git a/packages/test/test-utils/src/testContainerRuntimeFactory.ts b/packages/test/test-utils/src/testContainerRuntimeFactory.ts index 05c7db8efdae..5890c9473bef 100644 --- a/packages/test/test-utils/src/testContainerRuntimeFactory.ts +++ b/packages/test/test-utils/src/testContainerRuntimeFactory.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import { defaultRouteRequestHandler } from "@fluidframework/aqueduct"; import { IContainerContext, IRuntime } from "@fluidframework/container-definitions"; import { ContainerRuntime, @@ -11,9 +10,51 @@ import { DefaultSummaryConfiguration, } from "@fluidframework/container-runtime"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; +import { + FluidObject, + IFluidHandleContext, + IRequest, + IResponse, +} from "@fluidframework/core-interfaces"; +// eslint-disable-next-line import/no-deprecated import { buildRuntimeRequestHandler, RuntimeRequestHandler } from "@fluidframework/request-handler"; -import { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions"; -import { RuntimeFactoryHelper } from "@fluidframework/runtime-utils"; +import { + IFluidDataStoreFactory, + NamedFluidDataStoreRegistryEntries, +} from "@fluidframework/runtime-definitions"; +import { RequestParser, RuntimeFactoryHelper } from "@fluidframework/runtime-utils"; + +interface backCompat_IFluidRouter { + IFluidRouter?: backCompat_IFluidRouter; + request(request: IRequest): Promise; +} + +const backCompat_DefaultRouteRequestHandler = (defaultRootId: string) => { + return async (request: IRequest, runtime: IContainerRuntime) => { + const parser = RequestParser.create(request); + if (parser.pathParts.length === 0) { + return ( + runtime as any as Required> + ).IFluidHandleContext.resolveHandle({ + url: `/${defaultRootId}${parser.query}`, + headers: request.headers, + }); + } + return undefined; // continue search + }; +}; + +interface backCompat_ContainerRuntime { + load( + context: IContainerContext, + registryEntries: NamedFluidDataStoreRegistryEntries, + requestHandler?: (request: IRequest, runtime: IContainerRuntime) => Promise, + runtimeOptions?: IContainerRuntimeOptions, + containerScope?: FluidObject, + existing?: boolean, + containerRuntimeCtor?: typeof ContainerRuntime, + ): Promise; +} /** * Create a container runtime factory class that allows you to set runtime options @@ -56,7 +97,14 @@ export const createTestContainerRuntimeFactory = ( // Note: We use the deprecated `getRootDataStore` from v1.X here to allow for cross-major version compat // testing. Can be removed when we no longer support v1.X. await (runtime.getAliasedDataStoreEntryPoint?.("default") ?? - runtime.getRootDataStore("default")); + ( + runtime as any as { + getRootDataStore( + id: string, + wait?: boolean, + ): Promise; + } + ).getRootDataStore("default")); } async preInitialize( @@ -66,14 +114,14 @@ export const createTestContainerRuntimeFactory = ( if (containerRuntimeCtor.loadRuntime === undefined) { // Note: We use the deprecated `load` from v1.X here to allow for cross-major version compat testing. // Can be removed when we no longer support v1.X. - return containerRuntimeCtor.load( + return (containerRuntimeCtor as any as backCompat_ContainerRuntime).load( context, [ ["default", Promise.resolve(this.dataStoreFactory)], [this.type, Promise.resolve(this.dataStoreFactory)], ], buildRuntimeRequestHandler( - defaultRouteRequestHandler("default"), + backCompat_DefaultRouteRequestHandler("default"), ...this.requestHandlers, ), this.runtimeOptions, @@ -88,6 +136,17 @@ export const createTestContainerRuntimeFactory = ( } return entryPoint.get(); }; + const getDefaultObject = async (request: IRequest, runtime: IContainerRuntime) => { + const parser = RequestParser.create(request); + if (parser.pathParts.length === 0) { + // This cast is safe as ContainerRuntime.loadRuntime is called below + return (runtime as ContainerRuntime).resolveHandle({ + url: `/default${parser.query}`, + headers: request.headers, + }); + } + return undefined; // continue search + }; return containerRuntimeCtor.loadRuntime({ context, registryEntries: [ @@ -95,7 +154,7 @@ export const createTestContainerRuntimeFactory = ( [this.type, Promise.resolve(this.dataStoreFactory)], ], requestHandler: buildRuntimeRequestHandler( - defaultRouteRequestHandler("default"), + getDefaultObject, ...this.requestHandlers, ), provideEntryPoint, diff --git a/packages/test/test-utils/src/testFluidObject.ts b/packages/test/test-utils/src/testFluidObject.ts index d250c991751e..f90e1f2b9f40 100644 --- a/packages/test/test-utils/src/testFluidObject.ts +++ b/packages/test/test-utils/src/testFluidObject.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-deprecated -import { defaultFluidObjectRequestHandler } from "@fluidframework/aqueduct"; import { IRequest, IResponse, IFluidHandle } from "@fluidframework/core-interfaces"; import { FluidObjectHandle, @@ -19,6 +17,7 @@ import { } from "@fluidframework/runtime-definitions"; import { IFluidDataStoreRuntime, IChannelFactory } from "@fluidframework/datastore-definitions"; import { assert } from "@fluidframework/core-utils"; +import { create404Response } from "@fluidframework/runtime-utils"; import { ITestFluidObject } from "./interfaces"; /** @@ -36,13 +35,6 @@ export class TestFluidObject implements ITestFluidObject { return this; } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ - public get IFluidRouter() { - return this; - } - public get handle(): IFluidHandle { return this.innerHandle; } @@ -86,12 +78,10 @@ export class TestFluidObject implements ITestFluidObject { throw new Error(`Shared object with id ${id} not found.`); } - /** - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - */ public async request(request: IRequest): Promise { - // eslint-disable-next-line import/no-deprecated - return defaultFluidObjectRequestHandler(this, request); + return request.url === "" || request.url === "/" || request.url.startsWith("/?") + ? { mimeType: "fluid/object", status: 200, value: this } + : create404Response(request); } public async initialize(existing: boolean) { diff --git a/packages/test/test-version-utils/package.json b/packages/test/test-version-utils/package.json index fc9f0b430ebd..a587b2289583 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "End to end tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-version-utils/src/compatUtils.ts b/packages/test/test-version-utils/src/compatUtils.ts index 84a7f45c78ab..7579b1203d2a 100644 --- a/packages/test/test-version-utils/src/compatUtils.ts +++ b/packages/test/test-version-utils/src/compatUtils.ts @@ -4,7 +4,12 @@ */ import { FluidTestDriverConfig, createFluidTestDriver } from "@fluid-private/test-drivers"; -import { IFluidLoadable, IRequest } from "@fluidframework/core-interfaces"; +import { + FluidObject, + IFluidHandleContext, + IFluidLoadable, + IRequest, +} from "@fluidframework/core-interfaces"; import { IContainerRuntimeBase, IFluidDataStoreContext, @@ -121,8 +126,6 @@ export async function getVersionedTestObjectProviderFromApis( driverConfig?.config, apis.driver, ); - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); const getDataStoreFactoryFn = createGetDataStoreFactoryFunction(apis.dataRuntime); const containerFactoryFn = (containerOptions?: ITestContainerConfig) => { @@ -136,7 +139,6 @@ export async function getVersionedTestObjectProviderFromApis( TestDataObjectType, dataStoreFactory, containerOptions?.runtimeOptions, - [innerRequestHandler], ); }; @@ -196,7 +198,9 @@ export async function getCompatVersionedTestObjectProviderFromApis( ); const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); + ( + runtime as any as Required> + ).IFluidHandleContext.resolveHandle(request); const getDataStoreFactoryFn = createGetDataStoreFactoryFunction(apis.dataRuntime); const getDataStoreFactoryFnForLoading = createGetDataStoreFactoryFunction( diff --git a/packages/test/test-version-utils/src/packageVersion.ts b/packages/test/test-version-utils/src/packageVersion.ts index c2a3065af335..e2949a1c816b 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-version-utils/src/versionUtils.ts b/packages/test/test-version-utils/src/versionUtils.ts index a9b0ad4eea6b..0baceeb717d5 100644 --- a/packages/test/test-version-utils/src/versionUtils.ts +++ b/packages/test/test-version-utils/src/versionUtils.ts @@ -288,7 +288,7 @@ export const loadPackage = async (modulePath: string, pkg: string): Promise // Because we put legacy versions in a specific subfolder of node_modules (.legacy/), we need to reimplement // some of Node's module loading logic here. // It would be ideal to remove the need for this duplication (e.g. by using node:module APIs instead) if possible. - const pkgJson: { main?: string; exports?: string | Record } = JSON.parse( + const pkgJson: { main?: string; exports?: string | Record } = JSON.parse( readFileSync(path.join(pkgPath, "package.json"), { encoding: "utf8" }), ); // See: https://nodejs.org/docs/latest-v18.x/api/packages.html#package-entry-points @@ -308,7 +308,8 @@ export const loadPackage = async (modulePath: string, pkg: string): Promise if (typeof pkgJson.exports === "string") { primaryExport = pkgJson.exports; } else { - primaryExport = pkgJson.exports["."]; + const exp = pkgJson.exports["."]; + primaryExport = typeof exp === "string" ? exp : exp.require.default; if (primaryExport === undefined) { throw new Error(`Package ${pkg} defined subpath exports but no '.' entry.`); } diff --git a/packages/tools/devtools/devtools-browser-extension/package.json b/packages/tools/devtools/devtools-browser-extension/package.json index 27a8a67f04e9..1687874e1dd9 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "A browser extension for visualizing Fluid Framework stats and operations", "homepage": "https://fluidframework.com", diff --git a/packages/tools/devtools/devtools-core/api-report/devtools-core.api.md b/packages/tools/devtools/devtools-core/api-report/devtools-core.api.md index 0a6b1b252f5e..342c3887c64a 100644 --- a/packages/tools/devtools/devtools-core/api-report/devtools-core.api.md +++ b/packages/tools/devtools/devtools-core/api-report/devtools-core.api.md @@ -442,7 +442,7 @@ export interface MessageLoggingOptions { } // @internal -export type Primitive = bigint | number | boolean | null | string | symbol | undefined; +export type Primitive = number | boolean | null | string | undefined; // @internal export namespace RootDataVisualizations { diff --git a/packages/tools/devtools/devtools-core/package.json b/packages/tools/devtools/devtools-core/package.json index 48654bdaa5c0..212edf92ba40 100644 --- a/packages/tools/devtools/devtools-core/package.json +++ b/packages/tools/devtools/devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/devtools-core", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Framework developer tools core functionality", "homepage": "https://fluidframework.com", "repository": { @@ -127,6 +127,49 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_ContainerDevtoolsProps": { + "backCompat": false + }, + "InterfaceDeclaration_FluidDevtoolsProps": { + "backCompat": false + }, + "InterfaceDeclaration_FluidHandleNode": { + "forwardCompat": false + }, + "InterfaceDeclaration_FluidObjectNodeBase": { + "forwardCompat": false + }, + "InterfaceDeclaration_TreeNodeBase": { + "forwardCompat": false + }, + "InterfaceDeclaration_UnknownObjectNode": { + "forwardCompat": false + }, + "InterfaceDeclaration_ValueNodeBase": { + "forwardCompat": false + }, + "InterfaceDeclaration_VisualNodeBase": { + "forwardCompat": false + }, + "InterfaceDeclaration_VisualTreeNode": { + "forwardCompat": false + }, + "InterfaceDeclaration_VisualValueNode": { + "forwardCompat": false + }, + "TypeAliasDeclaration_Primitive": { + "forwardCompat": false + }, + "TypeAliasDeclaration_RootHandleNode": { + "forwardCompat": false + }, + "TypeAliasDeclaration_VisualChildNode": { + "forwardCompat": false + }, + "TypeAliasDeclaration_VisualNode": { + "forwardCompat": false + } + } } } diff --git a/packages/tools/devtools/devtools-core/src/data-visualization/VisualTree.ts b/packages/tools/devtools/devtools-core/src/data-visualization/VisualTree.ts index e02a2ca3af73..efd68a7c0ef9 100644 --- a/packages/tools/devtools/devtools-core/src/data-visualization/VisualTree.ts +++ b/packages/tools/devtools/devtools-core/src/data-visualization/VisualTree.ts @@ -33,14 +33,14 @@ export enum VisualNodeKind { } /** - * Type union representing TypeScript primitives. + * Type union representing TypeScript primitives supported in DDSes. * * @remarks Used for data / metadata in {@link VisualNodeBase}s. * * @internal */ // eslint-disable-next-line @rushstack/no-new-null -export type Primitive = bigint | number | boolean | null | string | symbol | undefined; +export type Primitive = number | boolean | null | string | undefined; /** * Base interface for all {@link VisualNode}s. diff --git a/packages/tools/devtools/devtools-core/src/packageVersion.ts b/packages/tools/devtools/devtools-core/src/packageVersion.ts index e6dc3370ca6a..fceb3e42c7cc 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 = "@fluid-experimental/devtools-core"; -export const pkgVersion = "2.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.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 944ae5d98528..e91c1dbbb4de 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 @@ -523,6 +523,7 @@ declare function get_current_InterfaceDeclaration_ContainerDevtoolsProps(): declare function use_old_InterfaceDeclaration_ContainerDevtoolsProps( use: TypeOnly): void; use_old_InterfaceDeclaration_ContainerDevtoolsProps( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ContainerDevtoolsProps()); /* @@ -1531,6 +1532,7 @@ declare function get_current_InterfaceDeclaration_FluidDevtoolsProps(): declare function use_old_InterfaceDeclaration_FluidDevtoolsProps( use: TypeOnly): void; use_old_InterfaceDeclaration_FluidDevtoolsProps( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_FluidDevtoolsProps()); /* @@ -1543,6 +1545,7 @@ declare function get_old_InterfaceDeclaration_FluidHandleNode(): declare function use_current_InterfaceDeclaration_FluidHandleNode( use: TypeOnly): void; use_current_InterfaceDeclaration_FluidHandleNode( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_FluidHandleNode()); /* @@ -1615,6 +1618,7 @@ declare function get_old_InterfaceDeclaration_FluidObjectNodeBase(): declare function use_current_InterfaceDeclaration_FluidObjectNodeBase( use: TypeOnly): void; use_current_InterfaceDeclaration_FluidObjectNodeBase( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_FluidObjectNodeBase()); /* @@ -2695,6 +2699,7 @@ declare function get_old_TypeAliasDeclaration_Primitive(): declare function use_current_TypeAliasDeclaration_Primitive( use: TypeOnly): void; use_current_TypeAliasDeclaration_Primitive( + // @ts-expect-error compatibility expected to be broken get_old_TypeAliasDeclaration_Primitive()); /* @@ -2815,6 +2820,7 @@ declare function get_old_TypeAliasDeclaration_RootHandleNode(): declare function use_current_TypeAliasDeclaration_RootHandleNode( use: TypeOnly): void; use_current_TypeAliasDeclaration_RootHandleNode( + // @ts-expect-error compatibility expected to be broken get_old_TypeAliasDeclaration_RootHandleNode()); /* @@ -3079,6 +3085,7 @@ declare function get_old_InterfaceDeclaration_TreeNodeBase(): declare function use_current_InterfaceDeclaration_TreeNodeBase( use: TypeOnly): void; use_current_InterfaceDeclaration_TreeNodeBase( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_TreeNodeBase()); /* @@ -3103,6 +3110,7 @@ declare function get_old_InterfaceDeclaration_UnknownObjectNode(): declare function use_current_InterfaceDeclaration_UnknownObjectNode( use: TypeOnly): void; use_current_InterfaceDeclaration_UnknownObjectNode( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_UnknownObjectNode()); /* @@ -3127,6 +3135,7 @@ declare function get_old_InterfaceDeclaration_ValueNodeBase(): declare function use_current_InterfaceDeclaration_ValueNodeBase( use: TypeOnly): void; use_current_InterfaceDeclaration_ValueNodeBase( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_ValueNodeBase()); /* @@ -3151,6 +3160,7 @@ declare function get_old_TypeAliasDeclaration_VisualChildNode(): declare function use_current_TypeAliasDeclaration_VisualChildNode( use: TypeOnly): void; use_current_TypeAliasDeclaration_VisualChildNode( + // @ts-expect-error compatibility expected to be broken get_old_TypeAliasDeclaration_VisualChildNode()); /* @@ -3175,6 +3185,7 @@ declare function get_old_TypeAliasDeclaration_VisualNode(): declare function use_current_TypeAliasDeclaration_VisualNode( use: TypeOnly): void; use_current_TypeAliasDeclaration_VisualNode( + // @ts-expect-error compatibility expected to be broken get_old_TypeAliasDeclaration_VisualNode()); /* @@ -3199,6 +3210,7 @@ declare function get_old_InterfaceDeclaration_VisualNodeBase(): declare function use_current_InterfaceDeclaration_VisualNodeBase( use: TypeOnly): void; use_current_InterfaceDeclaration_VisualNodeBase( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_VisualNodeBase()); /* @@ -3247,6 +3259,7 @@ declare function get_old_InterfaceDeclaration_VisualTreeNode(): declare function use_current_InterfaceDeclaration_VisualTreeNode( use: TypeOnly): void; use_current_InterfaceDeclaration_VisualTreeNode( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_VisualTreeNode()); /* @@ -3271,6 +3284,7 @@ declare function get_old_InterfaceDeclaration_VisualValueNode(): declare function use_current_InterfaceDeclaration_VisualValueNode( use: TypeOnly): void; use_current_InterfaceDeclaration_VisualValueNode( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_VisualValueNode()); /* diff --git a/packages/tools/devtools/devtools-example/package.json b/packages/tools/devtools/devtools-example/package.json index 2f538bebd2ed..9533db90cbc6 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "An example application demonstrating how Fluid's devtools can be integrated into an application", "homepage": "https://fluidframework.com", diff --git a/packages/tools/devtools/devtools-view/package.json b/packages/tools/devtools/devtools-view/package.json index 270dbc87bdc0..a0ad3d29b57d 100644 --- a/packages/tools/devtools/devtools-view/package.json +++ b/packages/tools/devtools/devtools-view/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/devtools-view", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Contains a visualization suite for use alongside the Fluid Devtools", "homepage": "https://fluidframework.com", diff --git a/packages/tools/devtools/devtools-view/src/components/data-visualization/CommonInterfaces.ts b/packages/tools/devtools/devtools-view/src/components/data-visualization/CommonInterfaces.ts index afc6906d3712..4664192c0aba 100644 --- a/packages/tools/devtools/devtools-view/src/components/data-visualization/CommonInterfaces.ts +++ b/packages/tools/devtools/devtools-view/src/components/data-visualization/CommonInterfaces.ts @@ -36,5 +36,5 @@ export interface DataVisualizationTreeProps); + edit(newValue: Serializable); } diff --git a/packages/tools/devtools/devtools/package.json b/packages/tools/devtools/devtools/package.json index da0e0b876a5d..279856c474e2 100644 --- a/packages/tools/devtools/devtools/package.json +++ b/packages/tools/devtools/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-experimental/devtools", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Framework developer tools", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/fetch-tool/package.json b/packages/tools/fetch-tool/package.json index cc2259638a56..08aba1f26aab 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Console tool to fetch Fluid data from relay service", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/fluid-runner/package.json b/packages/tools/fluid-runner/package.json index e16b1fe14eed..6cedc46fca4c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Utility for running various functionality inside a Fluid Framework environment", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/replay-tool/package.json b/packages/tools/replay-tool/package.json index 4d1220b32e04..184ec49e62fd 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "A tool that lets the user to replay ops.", "homepage": "https://fluidframework.com", diff --git a/packages/tools/webpack-fluid-loader/package.json b/packages/tools/webpack-fluid-loader/package.json index 013c65826ada..f4f0a79dff7d 100644 --- a/packages/tools/webpack-fluid-loader/package.json +++ b/packages/tools/webpack-fluid-loader/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-tools/webpack-fluid-loader", - "version": "2.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid object loader for webpack-dev-server", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/webpack-fluid-loader/src/loader.ts b/packages/tools/webpack-fluid-loader/src/loader.ts index 6f4b2e8a7f42..701f3f747186 100644 --- a/packages/tools/webpack-fluid-loader/src/loader.ts +++ b/packages/tools/webpack-fluid-loader/src/loader.ts @@ -380,18 +380,7 @@ async function getFluidObjectAndRender(container: IContainer, url: string, div: entryPoint === undefined || (entryPoint as IFluidMountableViewEntryPoint).getMountableDefaultView === undefined ) { - const response = await container.request({ - headers: { - mountableView: true, - }, - url, - }); - - if (response.status !== 200 || response.mimeType !== "fluid/object") { - return false; - } - - fluidObject = response.value; + throw new Error("entryPoint was not defined or is not properly formatted"); } else { fluidObject = await (entryPoint as IFluidMountableViewEntryPoint).getMountableDefaultView( // Remove starting "//" diff --git a/packages/utils/odsp-doclib-utils/package.json b/packages/utils/odsp-doclib-utils/package.json index 457dff737cdf..f610e297927c 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "ODSP utilities", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/utils/odsp-doclib-utils/src/packageVersion.ts b/packages/utils/odsp-doclib-utils/src/packageVersion.ts index ac0eeac1a9c2..2b725353a384 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/utils/telemetry-utils/package.json b/packages/utils/telemetry-utils/package.json index 6d3a499ff794..01a4dc38e4ec 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Collection of telemetry relates utilities for Fluid", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/utils/tool-utils/package.json b/packages/utils/tool-utils/package.json index 20882d42070c..be6df81aae45 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.0.0-internal.7.4.0", + "version": "2.0.0-internal.8.0.0", "description": "Common utilities for Fluid tools", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/utils/tool-utils/src/packageVersion.ts b/packages/utils/tool-utils/src/packageVersion.ts index d54477f217f5..239b7e51ab05 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.0.0-internal.7.4.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e229ced186b8..ed8dd7f0e3f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1867,7 +1867,6 @@ importers: '@fluidframework/datastore-definitions': workspace:~ '@fluidframework/eslint-config-fluid': ^3.1.0 '@fluidframework/map': workspace:~ - '@fluidframework/merge-tree': workspace:~ '@fluidframework/request-handler': workspace:~ '@fluidframework/runtime-definitions': workspace:~ '@fluidframework/runtime-utils': workspace:~ @@ -1902,7 +1901,6 @@ importers: '@fluidframework/datastore': link:../../../packages/runtime/datastore '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions '@fluidframework/map': link:../../../packages/dds/map - '@fluidframework/merge-tree': link:../../../packages/dds/merge-tree '@fluidframework/request-handler': link:../../../packages/framework/request-handler '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions '@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils @@ -2083,7 +2081,6 @@ importers: '@fluidframework/container-definitions': workspace:~ '@fluidframework/core-interfaces': workspace:~ '@fluidframework/eslint-config-fluid': ^3.1.0 - '@fluidframework/merge-tree': workspace:~ '@fluidframework/runtime-definitions': workspace:~ '@fluidframework/sequence': workspace:~ '@types/react': ^17.0.44 @@ -2112,7 +2109,6 @@ importers: '@fluidframework/aqueduct': link:../../../packages/framework/aqueduct '@fluidframework/container-definitions': link:../../../packages/common/container-definitions '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces - '@fluidframework/merge-tree': link:../../../packages/dds/merge-tree '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions '@fluidframework/sequence': link:../../../packages/dds/sequence monaco-editor: 0.30.1 @@ -2792,123 +2788,6 @@ importers: webpack-dev-server: 4.6.0_xufw7vsm4hgw2efzchq5tzqpge webpack-merge: 5.10.0 - examples/data-objects/shared-text: - specifiers: - '@fluid-example/example-utils': workspace:~ - '@fluid-internal/client-utils': workspace:~ - '@fluid-tools/webpack-fluid-loader': workspace:~ - '@fluidframework/aqueduct': workspace:~ - '@fluidframework/build-common': ^2.0.3 - '@fluidframework/build-tools': ^0.28.0 - '@fluidframework/container-definitions': workspace:~ - '@fluidframework/container-runtime': workspace:~ - '@fluidframework/core-interfaces': workspace:~ - '@fluidframework/core-utils': workspace:~ - '@fluidframework/datastore': workspace:~ - '@fluidframework/datastore-definitions': workspace:~ - '@fluidframework/map': workspace:~ - '@fluidframework/merge-tree': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 - '@fluidframework/request-handler': workspace:~ - '@fluidframework/runtime-definitions': workspace:~ - '@fluidframework/runtime-utils': workspace:~ - '@fluidframework/sequence': workspace:~ - '@fluidframework/test-tools': ^1.0.195075 - '@fluidframework/undo-redo': workspace:~ - '@fluidframework/view-interfaces': workspace:~ - '@types/expect-puppeteer': 2.2.1 - '@types/jest': 29.5.3 - '@types/jest-environment-puppeteer': 2.2.0 - '@types/loader-utils': ^1 - '@types/node': ^18.19.0 - '@types/prop-types': ^15 - '@types/puppeteer': 1.3.0 - '@types/react': ^17.0.44 - '@types/react-dom': ^17.0.18 - bootstrap: ^3.3.7 - cross-env: ^7.0.3 - css-loader: ^1.0.0 - debug: ^4.3.4 - eslint: ~8.50.0 - events: ^3.1.0 - jest: ^29.6.2 - jest-junit: ^10.0.0 - jest-puppeteer: ^6.2.0 - jsdom: ^16.7.0 - prettier: ~3.0.3 - process: ^0.11.10 - puppeteer: ^17.1.3 - react: ^17.0.1 - react-dom: ^17.0.1 - rimraf: ^4.4.0 - source-map-loader: ^2.0.0 - style-loader: ^1.0.0 - ts-loader: ^9.3.0 - typescript: ~5.1.6 - url-loader: ^2.1.0 - webpack: ^5.82.0 - webpack-cli: ^4.9.2 - webpack-dev-server: ~4.6.0 - webpack-merge: ^5.8.0 - dependencies: - '@fluid-example/example-utils': link:../../utils/example-utils - '@fluid-internal/client-utils': link:../../../packages/common/client-utils - '@fluidframework/aqueduct': link:../../../packages/framework/aqueduct - '@fluidframework/container-definitions': link:../../../packages/common/container-definitions - '@fluidframework/container-runtime': link:../../../packages/runtime/container-runtime - '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces - '@fluidframework/core-utils': link:../../../packages/common/core-utils - '@fluidframework/datastore': link:../../../packages/runtime/datastore - '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions - '@fluidframework/map': link:../../../packages/dds/map - '@fluidframework/merge-tree': link:../../../packages/dds/merge-tree - '@fluidframework/protocol-definitions': 3.0.0 - '@fluidframework/request-handler': link:../../../packages/framework/request-handler - '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions - '@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils - '@fluidframework/sequence': link:../../../packages/dds/sequence - '@fluidframework/undo-redo': link:../../../packages/framework/undo-redo - '@fluidframework/view-interfaces': link:../../../packages/framework/view-interfaces - bootstrap: 3.4.1 - debug: 4.3.4 - events: 3.3.0 - react: 17.0.2 - react-dom: 17.0.2_react@17.0.2 - devDependencies: - '@fluid-tools/webpack-fluid-loader': link:../../../packages/tools/webpack-fluid-loader - '@fluidframework/build-common': 2.0.3 - '@fluidframework/build-tools': 0.28.0_h467wi3sy6j67ifywrn7x7qf4m - '@fluidframework/test-tools': 1.0.195075 - '@types/expect-puppeteer': 2.2.1 - '@types/jest': 29.5.3 - '@types/jest-environment-puppeteer': 2.2.0 - '@types/loader-utils': 1.1.9 - '@types/node': 18.19.1 - '@types/prop-types': 15.7.11 - '@types/puppeteer': 1.3.0 - '@types/react': 17.0.71 - '@types/react-dom': 17.0.25 - cross-env: 7.0.3 - css-loader: 1.0.1_webpack@5.89.0 - eslint: 8.50.0 - jest: 29.7.0_@types+node@18.19.1 - jest-junit: 10.0.0 - jest-puppeteer: 6.2.0_c74ur6dz4zjug5kosl76nqtepy - jsdom: 16.7.0 - prettier: 3.0.3 - process: 0.11.10 - puppeteer: 17.1.3 - rimraf: 4.4.1 - source-map-loader: 2.0.2_webpack@5.89.0 - style-loader: 1.3.0_webpack@5.89.0 - ts-loader: 9.5.1_535b6rdv6xzubhvm4whegmtnju - typescript: 5.1.6 - url-loader: 2.3.0_webpack@5.89.0 - webpack: 5.89.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_o3vqr5s7sd4b3hfy35jxofofzu - webpack-dev-server: 4.6.0_rd5rsqvehm425nt6upzhjh73je - webpack-merge: 5.10.0 - examples/data-objects/smde: specifiers: '@fluid-example/example-utils': workspace:~ @@ -2925,7 +2804,6 @@ importers: '@fluidframework/datastore-definitions': workspace:~ '@fluidframework/eslint-config-fluid': ^3.1.0 '@fluidframework/map': workspace:~ - '@fluidframework/merge-tree': workspace:~ '@fluidframework/request-handler': workspace:~ '@fluidframework/runtime-definitions': workspace:~ '@fluidframework/runtime-utils': workspace:~ @@ -2960,7 +2838,6 @@ importers: '@fluidframework/datastore': link:../../../packages/runtime/datastore '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions '@fluidframework/map': link:../../../packages/dds/map - '@fluidframework/merge-tree': link:../../../packages/dds/merge-tree '@fluidframework/request-handler': link:../../../packages/framework/request-handler '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions '@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils @@ -3002,7 +2879,6 @@ importers: '@fluidframework/core-utils': workspace:~ '@fluidframework/datastore-definitions': workspace:~ '@fluidframework/eslint-config-fluid': ^3.1.0 - '@fluidframework/merge-tree': workspace:~ '@fluidframework/mocha-test-setup': workspace:~ '@fluidframework/protocol-definitions': ^3.0.0 '@fluidframework/request-handler': workspace:~ @@ -3031,7 +2907,6 @@ importers: '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces '@fluidframework/core-utils': link:../../../packages/common/core-utils '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions - '@fluidframework/merge-tree': link:../../../packages/dds/merge-tree '@fluidframework/protocol-definitions': 3.0.0 '@fluidframework/request-handler': link:../../../packages/framework/request-handler '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions @@ -3077,6 +2952,7 @@ importers: '@fluidframework/sequence': workspace:~ '@fluidframework/view-interfaces': workspace:~ '@tiny-calc/micro': 0.0.0-alpha.5 + '@tiny-calc/nano': 0.0.0-alpha.5 '@types/node': ^18.19.0 '@types/react': ^17.0.44 copyfiles: ^2.4.1 @@ -3105,6 +2981,7 @@ importers: '@fluidframework/sequence': link:../../../packages/dds/sequence '@fluidframework/view-interfaces': link:../../../packages/framework/view-interfaces '@tiny-calc/micro': 0.0.0-alpha.5 + '@tiny-calc/nano': 0.0.0-alpha.5 react: 17.0.2 devDependencies: '@fluid-tools/webpack-fluid-loader': link:../../../packages/tools/webpack-fluid-loader @@ -6967,6 +6844,7 @@ importers: '@types/mocha': ^9.1.1 '@types/node': ^18.19.0 '@types/random-js': ^1.0.31 + '@types/uuid': ^9.0.2 c8: ^7.7.1 copyfiles: ^2.4.1 cross-env: ^7.0.3 @@ -7010,6 +6888,7 @@ importers: '@types/mocha': 9.1.1 '@types/node': 18.19.1 '@types/random-js': 1.0.31 + '@types/uuid': 9.0.7 c8: 7.14.0 copyfiles: 2.4.1 cross-env: 7.0.3 @@ -8552,6 +8431,7 @@ importers: '@fluidframework/build-tools': ^0.28.0 '@fluidframework/container-definitions': workspace:~ '@fluidframework/container-loader': workspace:~ + '@fluidframework/container-runtime': workspace:~ '@fluidframework/container-runtime-definitions': workspace:~ '@fluidframework/core-interfaces': workspace:~ '@fluidframework/datastore-definitions': workspace:~ @@ -8584,6 +8464,7 @@ importers: '@fluidframework/aqueduct': link:../aqueduct '@fluidframework/container-definitions': link:../../common/container-definitions '@fluidframework/container-loader': link:../../loader/container-loader + '@fluidframework/container-runtime': link:../../runtime/container-runtime '@fluidframework/container-runtime-definitions': link:../../runtime/container-runtime-definitions '@fluidframework/core-interfaces': link:../../common/core-interfaces '@fluidframework/datastore-definitions': link:../../runtime/datastore-definitions @@ -9168,63 +9049,6 @@ importers: sinon: 7.5.0 typescript: 5.1.6 - packages/loader/location-redirection-utils: - specifiers: - '@arethetypeswrong/cli': ^0.13.3 - '@fluid-tools/build-cli': ^0.28.0 - '@fluidframework/build-common': ^2.0.3 - '@fluidframework/build-tools': ^0.28.0 - '@fluidframework/container-loader': workspace:~ - '@fluidframework/core-interfaces': workspace:~ - '@fluidframework/driver-definitions': workspace:~ - '@fluidframework/eslint-config-fluid': ^3.1.0 - '@fluidframework/location-redirection-utils-previous': npm:@fluidframework/location-redirection-utils@2.0.0-internal.7.2.0 - '@fluidframework/mocha-test-setup': workspace:~ - '@fluidframework/telemetry-utils': workspace:~ - '@fluidframework/test-runtime-utils': workspace:~ - '@microsoft/api-extractor': ^7.38.3 - '@types/mocha': ^9.1.1 - '@types/node': ^18.19.0 - c8: ^7.7.1 - copyfiles: ^2.4.1 - cross-env: ^7.0.3 - eslint: ~8.50.0 - mocha: ^10.2.0 - mocha-json-output-reporter: ^2.0.1 - mocha-multi-reporters: ^1.5.1 - moment: ^2.21.0 - prettier: ~3.0.3 - rimraf: ^4.4.0 - typescript: ~5.1.6 - dependencies: - '@fluidframework/container-loader': link:../container-loader - '@fluidframework/core-interfaces': link:../../common/core-interfaces - '@fluidframework/driver-definitions': link:../../common/driver-definitions - '@fluidframework/telemetry-utils': link:../../utils/telemetry-utils - devDependencies: - '@arethetypeswrong/cli': 0.13.3 - '@fluid-tools/build-cli': 0.28.0_mhw3tufvtaceew37hrkkevjcli - '@fluidframework/build-common': 2.0.3 - '@fluidframework/build-tools': 0.28.0_@types+node@18.19.1 - '@fluidframework/eslint-config-fluid': 3.1.0_loebgezstcsvd2poh2d55fifke - '@fluidframework/location-redirection-utils-previous': /@fluidframework/location-redirection-utils/2.0.0-internal.7.2.0 - '@fluidframework/mocha-test-setup': link:../../test/mocha-test-setup - '@fluidframework/test-runtime-utils': link:../../runtime/test-runtime-utils - '@microsoft/api-extractor': 7.38.3_veevzrg6trzjzkr6gaha4thdmm_@types+node@18.19.1 - '@types/mocha': 9.1.1 - '@types/node': 18.19.1 - c8: 7.14.0 - copyfiles: 2.4.1 - cross-env: 7.0.3 - eslint: 8.50.0 - mocha: 10.2.0 - mocha-json-output-reporter: 2.1.0_mocha@10.2.0+moment@2.29.4 - mocha-multi-reporters: 1.5.1_mocha@10.2.0 - moment: 2.29.4 - prettier: 3.0.3 - rimraf: 4.4.1 - typescript: 5.1.6 - packages/loader/test-loader-utils: specifiers: '@fluid-internal/client-utils': workspace:~ @@ -17041,17 +16865,6 @@ packages: - utf-8-validate dev: true - /@fluidframework/location-redirection-utils/2.0.0-internal.7.2.0: - resolution: {integrity: sha512-dL2/nuBNzh/RfcwQ/dgUYlFnK5r5okLw/mAl0UBFI0kwbcBU3Wv7W2QYrx1YsrlRLsOzyZlSW4BgFCzEgYU0AQ==} - dependencies: - '@fluidframework/container-loader': 2.0.0-internal.7.2.2 - '@fluidframework/core-interfaces': 2.0.0-internal.7.2.2 - '@fluidframework/driver-definitions': 2.0.0-internal.7.2.2 - '@fluidframework/telemetry-utils': 2.0.0-internal.7.2.2 - transitivePeerDependencies: - - supports-color - dev: true - /@fluidframework/map/2.0.0-internal.7.2.0: resolution: {integrity: sha512-wEIB8VM5SRqORjROU7gZF5K2YlE/fnv0KIgatVL9YeuHWWbjBp3D66q3Jvae2KdXKLsLYBHiMYUnl7iH5hTY4A==} dependencies: @@ -22527,13 +22340,6 @@ packages: '@types/node': 18.19.1 dev: true - /@types/loader-utils/1.1.9: - resolution: {integrity: sha512-2NrfPIjGI8ZuEBtMQHQ71Rf+dyZayiuf4EyJYBTvh5fbNLAmQ7AIpjbPRJ5CXEBJVfSgtoi02pI/yNaVAgxMYg==} - dependencies: - '@types/node': 18.19.1 - '@types/webpack': 4.41.38 - dev: true - /@types/lodash.isequal/4.5.8: resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} dependencies: @@ -24437,14 +24243,6 @@ packages: - debug dev: true - /axios/0.25.0_debug@4.3.4: - resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} - dependencies: - follow-redirects: 1.15.3_debug@4.3.4 - transitivePeerDependencies: - - debug - dev: true - /axios/0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: @@ -25052,11 +24850,6 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} dev: true - /bootstrap/3.4.1: - resolution: {integrity: sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==} - engines: {node: '>=6'} - dev: false - /boxen/1.3.0: resolution: {integrity: sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==} engines: {node: '>=4'} @@ -33058,21 +32851,6 @@ packages: - supports-color dev: true - /jest-dev-server/6.2.0_debug@4.3.4: - resolution: {integrity: sha512-ZWh8CuvxwjhYfvw4tGeftziqIvw/26R6AG3OTgNTQeXul8aZz48RQjDpnlDwnWX53jxJJl9fcigqIdSU5lYZuw==} - dependencies: - chalk: 4.1.2 - cwd: 0.10.0 - find-process: 1.4.7 - prompts: 2.4.2 - spawnd: 6.2.0 - tree-kill: 1.2.2 - wait-on: 6.0.1_debug@4.3.4 - transitivePeerDependencies: - - debug - - supports-color - dev: true - /jest-diff/29.7.0: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -33172,19 +32950,6 @@ packages: - supports-color dev: true - /jest-environment-puppeteer/6.2.0_debug@4.3.4: - resolution: {integrity: sha512-a/oSu6dO9D+XoDDe3ZY/0Sk79Jl2FcJl7Q0D+3x22l1eWNOYe4ikXnPGhtmNZ3mJIpuAVIX6LytA8EraOk/aqQ==} - dependencies: - chalk: 4.1.2 - cwd: 0.10.0 - jest-dev-server: 6.2.0_debug@4.3.4 - jest-environment-node: 27.5.1 - merge-deep: 3.0.3 - transitivePeerDependencies: - - debug - - supports-color - dev: true - /jest-get-type/24.9.0: resolution: {integrity: sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==} engines: {node: '>= 6'} @@ -33325,19 +33090,6 @@ packages: jest-resolve: 29.7.0 dev: true - /jest-puppeteer/6.2.0_c74ur6dz4zjug5kosl76nqtepy: - resolution: {integrity: sha512-4Ynkgcf9FkHlTNEpdsojGLb3KtsToWqjO6SCigxb9Qj3HzIqhzJzNbDJ/XhiciNDpqDe6KHW9sZ6fjHphNLr6g==} - peerDependencies: - puppeteer: '>= 1.5.0' - dependencies: - expect-puppeteer: 6.1.1 - jest-environment-puppeteer: 6.2.0_debug@4.3.4 - puppeteer: 17.1.3 - transitivePeerDependencies: - - debug - - supports-color - dev: true - /jest-puppeteer/6.2.0_puppeteer@17.1.3: resolution: {integrity: sha512-4Ynkgcf9FkHlTNEpdsojGLb3KtsToWqjO6SCigxb9Qj3HzIqhzJzNbDJ/XhiciNDpqDe6KHW9sZ6fjHphNLr6g==} peerDependencies: @@ -43869,20 +43621,6 @@ packages: - debug dev: true - /wait-on/6.0.1_debug@4.3.4: - resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} - engines: {node: '>=10.0.0'} - hasBin: true - dependencies: - axios: 0.25.0_debug@4.3.4 - joi: 17.11.0 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.1 - transitivePeerDependencies: - - debug - dev: true - /wait-on/7.0.1_debug@4.3.4: resolution: {integrity: sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==} engines: {node: '>=12.0.0'} @@ -44232,7 +43970,7 @@ packages: strip-ansi: 7.1.0 url: 0.11.3 webpack: 5.89.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_o3vqr5s7sd4b3hfy35jxofofzu + webpack-cli: 4.10.0_zuxfwx5l7f5r3sdy5pnh4au4ju webpack-dev-middleware: 5.3.3_webpack@5.89.0 ws: 8.14.2 transitivePeerDependencies: diff --git a/server/routerlicious/.changeset/blue-crews-cry.md b/server/routerlicious/.changeset/blue-crews-cry.md new file mode 100644 index 000000000000..1cf91fd8f72d --- /dev/null +++ b/server/routerlicious/.changeset/blue-crews-cry.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/server-services-client": major +--- + +Use RawAxiosRequestHeaders instead of AxiosRequestHeaders in BasicRestWrapper constructor. + +The `BasicRestWrapper` class constructor now uses `RawAxiosRequestHeaders` from the `axios` package instead of `AxiosRequestHeaders`. This applies to both the `defaultHeaders` and `refreshDefaultHeaders` arguments. + +This change was made in [#17419](https://github.com/microsoft/FluidFramework/pull/17419). diff --git a/server/routerlicious/.changeset/ready-colts-bake.md b/server/routerlicious/.changeset/ready-colts-bake.md new file mode 100644 index 000000000000..5f9f46c8bae5 --- /dev/null +++ b/server/routerlicious/.changeset/ready-colts-bake.md @@ -0,0 +1,13 @@ +--- +"@fluidframework/server-services-client": major +--- + +Export ITimeoutContext, getGlobalTimeoutContext, and setGlobalTimeoutContext from @fluidframework/server-services-client + +The `@fluidframework/server-services-client` package now exports the following items. These APIs are intended for internal use within the Fluid Framework only. They will be marked as internal APIs in a future release. + +- `ITimeoutContext`: Binds and tracks timeout info through a given codepath. The timeout can be checked manually to stop exit out of the codepath if the timeout has been exceeded. +- `getGlobalTimeoutContext`: Retrieves the global ITimeoutContext instance if available. If not available, returns a NullTimeoutContext instance which behaves as a no-op. +- `setGlobalTimeoutContext`: Sets the global ITimeoutContext instance. + +This change was made in [#17522](https://github.com/microsoft/FluidFramework/pull/17522). diff --git a/server/routerlicious/packages/kafka-orderer/package.json b/server/routerlicious/packages/kafka-orderer/package.json index de78439a1677..f4771a47fd01 100644 --- a/server/routerlicious/packages/kafka-orderer/package.json +++ b/server/routerlicious/packages/kafka-orderer/package.json @@ -28,7 +28,7 @@ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" }, "dependencies": { - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-core": "workspace:~" }, "devDependencies": { diff --git a/server/routerlicious/packages/lambdas-driver/package.json b/server/routerlicious/packages/lambdas-driver/package.json index f3ed41630d4f..f79aab9a8c4f 100644 --- a/server/routerlicious/packages/lambdas-driver/package.json +++ b/server/routerlicious/packages/lambdas-driver/package.json @@ -52,7 +52,7 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", diff --git a/server/routerlicious/packages/lambdas/package.json b/server/routerlicious/packages/lambdas/package.json index e5638ccaba20..2b7227539571 100644 --- a/server/routerlicious/packages/lambdas/package.json +++ b/server/routerlicious/packages/lambdas/package.json @@ -53,10 +53,10 @@ }, "dependencies": { "@fluidframework/common-definitions": "^1.0.0", - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-lambdas-driver": "workspace:~", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", diff --git a/server/routerlicious/packages/local-server/package.json b/server/routerlicious/packages/local-server/package.json index 08cdf03b6ec4..bd3e22d88de0 100644 --- a/server/routerlicious/packages/local-server/package.json +++ b/server/routerlicious/packages/local-server/package.json @@ -57,8 +57,8 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-lambdas": "workspace:~", "@fluidframework/server-memory-orderer": "workspace:~", "@fluidframework/server-services-client": "workspace:~", diff --git a/server/routerlicious/packages/memory-orderer/package.json b/server/routerlicious/packages/memory-orderer/package.json index b8cee2127740..704abbdb36aa 100644 --- a/server/routerlicious/packages/memory-orderer/package.json +++ b/server/routerlicious/packages/memory-orderer/package.json @@ -56,9 +56,9 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-lambdas": "workspace:~", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", diff --git a/server/routerlicious/packages/protocol-base/package.json b/server/routerlicious/packages/protocol-base/package.json index af982c195866..9cac726bb454 100644 --- a/server/routerlicious/packages/protocol-base/package.json +++ b/server/routerlicious/packages/protocol-base/package.json @@ -59,9 +59,9 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "events": "^3.1.0" }, "devDependencies": { diff --git a/server/routerlicious/packages/routerlicious-base/package.json b/server/routerlicious/packages/routerlicious-base/package.json index ca3532b12bef..c03700132269 100644 --- a/server/routerlicious/packages/routerlicious-base/package.json +++ b/server/routerlicious/packages/routerlicious-base/package.json @@ -50,9 +50,9 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-kafka-orderer": "workspace:~", "@fluidframework/server-lambdas": "workspace:~", "@fluidframework/server-lambdas-driver": "workspace:~", diff --git a/server/routerlicious/packages/routerlicious/package.json b/server/routerlicious/packages/routerlicious/package.json index 5647f1b234d9..159eb67a99d9 100644 --- a/server/routerlicious/packages/routerlicious/package.json +++ b/server/routerlicious/packages/routerlicious/package.json @@ -38,9 +38,9 @@ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-kafka-orderer": "workspace:~", "@fluidframework/server-lambdas": "workspace:~", "@fluidframework/server-lambdas-driver": "workspace:~", diff --git a/server/routerlicious/packages/services-client/api-report/server-services-client.api.md b/server/routerlicious/packages/services-client/api-report/server-services-client.api.md index 163c6925fd60..780982d638ab 100644 --- a/server/routerlicious/packages/services-client/api-report/server-services-client.api.md +++ b/server/routerlicious/packages/services-client/api-report/server-services-client.api.md @@ -93,7 +93,7 @@ export function generateUser(): IUser; // @internal (undocumented) export const getAuthorizationTokenFromCredentials: (credentials: ICredentials) => string; -// @internal (undocumented) +// @internal export const getGlobalTimeoutContext: () => ITimeoutContext; // @internal (undocumented) @@ -401,7 +401,7 @@ export interface ISummaryUploadManager { writeSummaryTree(summaryTree: api.ISummaryTree, parentHandle: string, summaryType: IWholeSummaryPayloadType, sequenceNumber?: number): Promise; } -// @internal (undocumented) +// @internal export interface ITimeoutContext { bindTimeout(maxDurationMs: number, callback: () => void): void; bindTimeoutAsync(maxDurationMs: number, callback: () => Promise): Promise; @@ -596,7 +596,7 @@ export abstract class RestWrapper { protected abstract request(options: AxiosRequestConfig, statusCode: number): Promise; } -// @internal (undocumented) +// @internal export const setGlobalTimeoutContext: (timeoutContext: ITimeoutContext) => void; // @internal diff --git a/server/routerlicious/packages/services-client/package.json b/server/routerlicious/packages/services-client/package.json index eb0775497048..4a20f981a6bd 100644 --- a/server/routerlicious/packages/services-client/package.json +++ b/server/routerlicious/packages/services-client/package.json @@ -59,10 +59,10 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "axios": "^1.6.2", "crc-32": "1.2.0", "debug": "^4.3.4", diff --git a/server/routerlicious/packages/services-client/src/timeoutContext.ts b/server/routerlicious/packages/services-client/src/timeoutContext.ts index 39857814faf1..912abde5b364 100644 --- a/server/routerlicious/packages/services-client/src/timeoutContext.ts +++ b/server/routerlicious/packages/services-client/src/timeoutContext.ts @@ -4,6 +4,9 @@ */ /** + * Binds and tracks timeout info through a given codepath. + * The timeout can be checked manually to stop exit out of the codepath if the timeout has been exceeded. + * * @internal */ export interface ITimeoutContext { @@ -43,12 +46,17 @@ declare var global: typeof globalThis; export const getGlobal = (): any => (typeof window !== "undefined" ? window : global); /** + * Retrieves the global ITimeoutContext instance if available. + * If not available, returns a NullTimeoutContext instance which behaves as a no-op. + * * @internal */ export const getGlobalTimeoutContext = () => (getGlobal()?.timeoutContext as ITimeoutContext | undefined) ?? nullTimeoutContext; /** + * Sets the global ITimeoutContext instance. + * * @internal */ export const setGlobalTimeoutContext = (timeoutContext: ITimeoutContext) => { diff --git a/server/routerlicious/packages/services-core/package.json b/server/routerlicious/packages/services-core/package.json index 488112e12405..216feaa31b98 100644 --- a/server/routerlicious/packages/services-core/package.json +++ b/server/routerlicious/packages/services-core/package.json @@ -29,9 +29,9 @@ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", "@types/nconf": "^0.10.2", diff --git a/server/routerlicious/packages/services-ordering-rdkafka/package.json b/server/routerlicious/packages/services-ordering-rdkafka/package.json index 1769f431e917..6643ccc53cdc 100644 --- a/server/routerlicious/packages/services-ordering-rdkafka/package.json +++ b/server/routerlicious/packages/services-ordering-rdkafka/package.json @@ -28,7 +28,7 @@ "typetests:prepare": "flub typetests --dir . --reset --previous --normalize" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", diff --git a/server/routerlicious/packages/services-shared/package.json b/server/routerlicious/packages/services-shared/package.json index a966733c70cf..660cdb259337 100644 --- a/server/routerlicious/packages/services-shared/package.json +++ b/server/routerlicious/packages/services-shared/package.json @@ -52,10 +52,10 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", diff --git a/server/routerlicious/packages/services-telemetry/package.json b/server/routerlicious/packages/services-telemetry/package.json index e410fe98b65b..41bf9cec4c00 100644 --- a/server/routerlicious/packages/services-telemetry/package.json +++ b/server/routerlicious/packages/services-telemetry/package.json @@ -52,7 +52,7 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "json-stringify-safe": "^5.0.1", "path-browserify": "^1.0.1", "serialize-error": "^8.1.0", diff --git a/server/routerlicious/packages/services-utils/package.json b/server/routerlicious/packages/services-utils/package.json index eeb20723e79a..35686324df05 100644 --- a/server/routerlicious/packages/services-utils/package.json +++ b/server/routerlicious/packages/services-utils/package.json @@ -52,7 +52,7 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", diff --git a/server/routerlicious/packages/services/package.json b/server/routerlicious/packages/services/package.json index cb7456496ee5..29a9979afb12 100644 --- a/server/routerlicious/packages/services/package.json +++ b/server/routerlicious/packages/services/package.json @@ -51,8 +51,8 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-ordering-kafkanode": "workspace:~", diff --git a/server/routerlicious/packages/test-utils/package.json b/server/routerlicious/packages/test-utils/package.json index 5ee03f161f60..232ec7a4e9a7 100644 --- a/server/routerlicious/packages/test-utils/package.json +++ b/server/routerlicious/packages/test-utils/package.json @@ -51,10 +51,10 @@ "temp-directory": "nyc/.nyc_output" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-services-client": "workspace:~", "@fluidframework/server-services-core": "workspace:~", "@fluidframework/server-services-telemetry": "workspace:~", diff --git a/server/routerlicious/packages/tinylicious/package.json b/server/routerlicious/packages/tinylicious/package.json index f00c9515e8b9..994d0643bbd1 100644 --- a/server/routerlicious/packages/tinylicious/package.json +++ b/server/routerlicious/packages/tinylicious/package.json @@ -36,10 +36,10 @@ "tsc": "tsc" }, "dependencies": { - "@fluidframework/common-utils": "^3.0.0", + "@fluidframework/common-utils": "3.0.0-220318", "@fluidframework/gitresources": "workspace:~", "@fluidframework/protocol-base": "workspace:~", - "@fluidframework/protocol-definitions": "^3.0.0", + "@fluidframework/protocol-definitions": "^3.1.0-220363", "@fluidframework/server-lambdas": "workspace:~", "@fluidframework/server-local-server": "workspace:~", "@fluidframework/server-memory-orderer": "workspace:~", diff --git a/server/routerlicious/pnpm-lock.yaml b/server/routerlicious/pnpm-lock.yaml index 2d62a9a5a3cd..b6df3fca9660 100644 --- a/server/routerlicious/pnpm-lock.yaml +++ b/server/routerlicious/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 '@fluidframework/eslint-config-fluid': ^2.0.0 - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-kafka-orderer-previous': npm:@fluidframework/server-kafka-orderer@2.0.0 '@fluidframework/server-services-core': workspace:~ '@types/node': ^18.17.1 @@ -95,7 +95,7 @@ importers: rimraf: ^4.4.0 typescript: ~4.5.5 dependencies: - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-core': link:../services-core devDependencies: '@fluid-tools/build-cli': 0.26.1_arb67uuw3qsmr24y5xn6pymdwm @@ -116,11 +116,11 @@ importers: '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 '@fluidframework/common-definitions': ^1.0.0 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-lambdas-driver': workspace:~ '@fluidframework/server-lambdas-previous': npm:@fluidframework/server-lambdas@2.0.0 '@fluidframework/server-services-client': workspace:~ @@ -161,10 +161,10 @@ importers: webpack: ^5.82.0 dependencies: '@fluidframework/common-definitions': 1.0.0 - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-lambdas-driver': link:../lambdas-driver '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core @@ -214,7 +214,7 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/server-lambdas-driver-previous': npm:@fluidframework/server-lambdas-driver@2.0.0 '@fluidframework/server-services-client': workspace:~ @@ -239,7 +239,7 @@ importers: serialize-error: ^8.1.0 typescript: ~4.5.5 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-telemetry': link:../services-telemetry @@ -273,9 +273,9 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-lambdas': workspace:~ '@fluidframework/server-local-server-previous': npm:@fluidframework/server-local-server@2.0.0 '@fluidframework/server-memory-orderer': workspace:~ @@ -307,8 +307,8 @@ importers: webpack: ^5.82.0 webpack-cli: ^4.9.2 dependencies: - '@fluidframework/common-utils': 3.0.0 - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-lambdas': link:../lambdas '@fluidframework/server-memory-orderer': link:../memory-orderer '@fluidframework/server-services-client': link:../services-client @@ -350,10 +350,10 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-lambdas': workspace:~ '@fluidframework/server-memory-orderer-previous': npm:@fluidframework/server-memory-orderer@2.0.0 '@fluidframework/server-services-client': workspace:~ @@ -383,9 +383,9 @@ importers: webpack: ^5.82.0 ws: ^7.4.6 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-lambdas': link:../lambdas '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core @@ -425,11 +425,11 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/protocol-base-previous': npm:@fluidframework/protocol-base@2.0.0 - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@microsoft/api-extractor': ^7.38.3 '@types/assert': ^1.5.1 '@types/mocha': ^10.0.1 @@ -444,9 +444,9 @@ importers: rimraf: ^4.4.0 typescript: ~4.5.5 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 events: 3.3.0 devDependencies: '@fluid-tools/build-cli': 0.26.1_arb67uuw3qsmr24y5xn6pymdwm @@ -472,10 +472,10 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-kafka-orderer': workspace:~ '@fluidframework/server-lambdas': workspace:~ '@fluidframework/server-lambdas-driver': workspace:~ @@ -503,9 +503,9 @@ importers: typescript: ~4.5.5 winston: ^3.6.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-kafka-orderer': link:../kafka-orderer '@fluidframework/server-lambdas': link:../lambdas '@fluidframework/server-lambdas-driver': link:../lambdas-driver @@ -543,10 +543,10 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-kafka-orderer': workspace:~ '@fluidframework/server-lambdas': workspace:~ '@fluidframework/server-lambdas-driver': workspace:~ @@ -607,9 +607,9 @@ importers: winston: ^3.6.0 ws: ^7.4.6 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-kafka-orderer': link:../kafka-orderer '@fluidframework/server-lambdas': link:../lambdas '@fluidframework/server-lambdas-driver': link:../lambdas-driver @@ -680,9 +680,9 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core': workspace:~ '@fluidframework/server-services-ordering-kafkanode': workspace:~ @@ -726,8 +726,8 @@ importers: uuid: ^9.0.0 winston: ^3.6.0 dependencies: - '@fluidframework/common-utils': 3.0.0 - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-ordering-kafkanode': link:../services-ordering-kafkanode @@ -781,11 +781,11 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client-previous': npm:@fluidframework/server-services-client@2.0.0 '@microsoft/api-extractor': ^7.38.3 '@types/debug': ^4.1.5 @@ -812,10 +812,10 @@ importers: typescript: ~4.5.5 uuid: ^9.0.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 axios: 1.6.2_debug@4.3.4 crc-32: 1.2.0 debug: 4.3.4 @@ -852,10 +852,10 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core-previous': npm:@fluidframework/server-services-core@2.0.0 '@fluidframework/server-services-telemetry': workspace:~ @@ -870,9 +870,9 @@ importers: rimraf: ^4.4.0 typescript: ~4.5.5 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-telemetry': link:../services-telemetry '@types/nconf': 0.10.3 @@ -950,7 +950,7 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core': workspace:~ @@ -973,7 +973,7 @@ importers: sinon: ^9.2.3 typescript: ~4.5.5 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-telemetry': link:../services-telemetry @@ -1046,11 +1046,11 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core': workspace:~ '@fluidframework/server-services-shared-previous': npm:@fluidframework/server-services-shared@2.0.0 @@ -1089,10 +1089,10 @@ importers: uuid: ^9.0.0 winston: ^3.6.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-telemetry': link:../services-telemetry @@ -1141,7 +1141,7 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/server-services-telemetry-previous': npm:@fluidframework/server-services-telemetry@2.0.0 '@types/mocha': ^10.0.1 @@ -1161,7 +1161,7 @@ importers: typescript: ~4.5.5 uuid: ^9.0.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 json-stringify-safe: 5.0.1 path-browserify: 1.0.1 serialize-error: 8.1.0 @@ -1191,7 +1191,7 @@ importers: '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 '@fluidframework/eslint-config-fluid': ^2.0.0 - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core': workspace:~ '@fluidframework/server-services-telemetry': workspace:~ @@ -1230,7 +1230,7 @@ importers: winston: ^3.6.0 winston-transport: ^4.5.0 dependencies: - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-telemetry': link:../services-telemetry @@ -1279,11 +1279,11 @@ importers: '@fluid-tools/build-cli': ^0.26.1 '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': ^0.26.1 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-services-client': workspace:~ '@fluidframework/server-services-core': workspace:~ '@fluidframework/server-services-telemetry': workspace:~ @@ -1308,10 +1308,10 @@ importers: typescript: ~4.5.5 uuid: ^9.0.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-services-client': link:../services-client '@fluidframework/server-services-core': link:../services-core '@fluidframework/server-services-telemetry': link:../services-telemetry @@ -1344,12 +1344,12 @@ importers: packages/tinylicious: specifiers: '@fluidframework/build-common': ^2.0.3 - '@fluidframework/common-utils': ^3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/eslint-config-fluid': ^2.0.0 '@fluidframework/gitresources': workspace:~ '@fluidframework/mocha-test-setup': ~2.0.0-internal.6.2.0 '@fluidframework/protocol-base': workspace:~ - '@fluidframework/protocol-definitions': ^3.0.0 + '@fluidframework/protocol-definitions': ^3.1.0-220363 '@fluidframework/server-lambdas': workspace:~ '@fluidframework/server-local-server': workspace:~ '@fluidframework/server-memory-orderer': workspace:~ @@ -1409,10 +1409,10 @@ importers: uuid: ^9.0.0 winston: ^3.6.0 dependencies: - '@fluidframework/common-utils': 3.0.0 + '@fluidframework/common-utils': 3.0.0-220318 '@fluidframework/gitresources': link:../gitresources '@fluidframework/protocol-base': link:../protocol-base - '@fluidframework/protocol-definitions': 3.0.0 + '@fluidframework/protocol-definitions': 3.1.0-220363 '@fluidframework/server-lambdas': link:../lambdas '@fluidframework/server-local-server': link:../local-server '@fluidframework/server-memory-orderer': link:../memory-orderer @@ -2825,6 +2825,19 @@ packages: events: 3.3.0 lodash: 4.17.21 sha.js: 2.4.11 + dev: true + + /@fluidframework/common-utils/3.0.0-220318: + resolution: {integrity: sha512-tDuL81/FxrjCY4CaAonLRaL1kkHYJCKF7upqzO5HgJ1zIeEzTt4XFtcX3ZsXvzp5Q3TvTy1CWye3qr8ELBB+nQ==} + dependencies: + '@fluidframework/common-definitions': 1.0.0 + '@types/events': 3.0.0 + base64-js: 1.5.1 + buffer: 6.0.3 + events: 3.3.0 + lodash: 4.17.21 + sha.js: 2.4.11 + dev: false /@fluidframework/core-interfaces/2.0.0-internal.7.0.0: resolution: {integrity: sha512-znZqbn0ew7aqrnjFf+sfNsnajjZ9z9PHycrfzkVfo6YAW1WKuTi7VmZwUyLGVDIAJ86qlQW4/kI9WL4OypzSFA==} @@ -2928,6 +2941,11 @@ packages: resolution: {integrity: sha512-FUIw5B5aTVJ831zAvsNUzKGFCkPoswmUeeYfZtVlq2wRpO2Ffm28TYhbkrAGFOtCd0BiByJDho6Oo+iZbNBgUA==} dependencies: '@fluidframework/common-definitions': 1.0.0 + dev: true + + /@fluidframework/protocol-definitions/3.1.0-220363: + resolution: {integrity: sha512-5V4UDx7azGSNriM322q9w1pl8QfFw1F5TrByiXm1hqManEV241hz3vK5X1E7nslP8d+YXHyVoORxuVn7B0KVIQ==} + dev: false /@fluidframework/server-kafka-orderer/2.0.0: resolution: {integrity: sha512-3Wn0XLecbz8kytyfE4mukFUUeoEEnzAFFfEBzjBelpkS6bbAZ/7TdLLJ34q9JS8Cnb5vqreFfJCaHbboIUe6YQ==}