From d405186713d8fb34414310a986fd81043c0dee07 Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Thu, 28 Sep 2023 13:03:11 -0700 Subject: [PATCH 01/50] [bump] client: 2.0.0-internal.7.1.0 => 2.0.0-internal.8.0.0 (major) (#17531) Bumped client from 2.0.0-internal.7.1.0 to 2.0.0-internal.8.0.0. --- azure/packages/azure-client/package.json | 2 +- azure/packages/azure-local-service/package.json | 2 +- azure/packages/azure-service-utils/package.json | 2 +- azure/packages/external-controller/package.json | 2 +- azure/packages/test/end-to-end-tests/package.json | 2 +- azure/packages/test/scenario-runner/package.json | 2 +- azure/packages/test/scenario-runner/src/packageVersion.ts | 2 +- examples/apps/attributable-map/package.json | 2 +- examples/apps/collaborative-textarea/package.json | 2 +- examples/apps/contact-collection/package.json | 2 +- examples/apps/data-object-grid/package.json | 2 +- examples/apps/presence-tracker/package.json | 2 +- examples/apps/task-selection/package.json | 2 +- examples/benchmarks/bubblebench/baseline/package.json | 2 +- examples/benchmarks/bubblebench/common/package.json | 2 +- .../benchmarks/bubblebench/editable-shared-tree/package.json | 2 +- examples/benchmarks/bubblebench/ot/package.json | 2 +- examples/benchmarks/bubblebench/sharedtree/package.json | 2 +- examples/benchmarks/odspsnapshotfetch-perftestapp/package.json | 2 +- examples/client-logger/app-insights-logger/package.json | 2 +- examples/data-objects/canvas/package.json | 2 +- examples/data-objects/clicker/package.json | 2 +- examples/data-objects/codemirror/package.json | 2 +- examples/data-objects/diceroller/package.json | 2 +- examples/data-objects/inventory-app/package.json | 2 +- examples/data-objects/monaco/package.json | 2 +- .../data-objects/multiview/constellation-model/package.json | 2 +- examples/data-objects/multiview/constellation-view/package.json | 2 +- examples/data-objects/multiview/container/package.json | 2 +- examples/data-objects/multiview/coordinate-model/package.json | 2 +- examples/data-objects/multiview/interface/package.json | 2 +- .../data-objects/multiview/plot-coordinate-view/package.json | 2 +- .../data-objects/multiview/slider-coordinate-view/package.json | 2 +- examples/data-objects/multiview/triangle-view/package.json | 2 +- examples/data-objects/prosemirror/package.json | 2 +- examples/data-objects/shared-text/package.json | 2 +- examples/data-objects/smde/package.json | 2 +- examples/data-objects/table-document/package.json | 2 +- examples/data-objects/table-view/package.json | 2 +- examples/data-objects/todo/package.json | 2 +- examples/data-objects/webflow/package.json | 2 +- examples/external-data/package.json | 2 +- examples/utils/bundle-size-tests/package.json | 2 +- examples/utils/example-utils/package.json | 2 +- examples/version-migration/live-schema-upgrade/package.json | 2 +- examples/version-migration/same-container/package.json | 2 +- examples/version-migration/schema-upgrade/package.json | 2 +- examples/view-integration/container-views/package.json | 2 +- examples/view-integration/external-views/package.json | 2 +- examples/view-integration/view-framework-sampler/package.json | 2 +- experimental/PropertyDDS/examples/partial-checkout/package.json | 2 +- .../PropertyDDS/examples/property-inspector/package.json | 2 +- experimental/PropertyDDS/examples/schemas/package.json | 2 +- experimental/PropertyDDS/packages/property-binder/package.json | 2 +- .../PropertyDDS/packages/property-changeset/package.json | 2 +- experimental/PropertyDDS/packages/property-common/package.json | 2 +- .../packages/property-common/platform-dependent/package.json | 2 +- experimental/PropertyDDS/packages/property-dds/package.json | 2 +- .../PropertyDDS/packages/property-inspector-table/package.json | 2 +- .../PropertyDDS/packages/property-properties/package.json | 2 +- experimental/PropertyDDS/packages/property-proxy/package.json | 2 +- experimental/PropertyDDS/packages/property-query/package.json | 2 +- .../packages/property-shared-tree-interop/package.json | 2 +- .../PropertyDDS/services/property-query-service/package.json | 2 +- experimental/dds/attributable-map/package.json | 2 +- experimental/dds/attributable-map/src/packageVersion.ts | 2 +- experimental/dds/ot/ot/package.json | 2 +- experimental/dds/ot/ot/src/packageVersion.ts | 2 +- experimental/dds/ot/sharejs/json1/package.json | 2 +- experimental/dds/ot/sharejs/json1/src/packageVersion.ts | 2 +- experimental/dds/sequence-deprecated/package.json | 2 +- experimental/dds/sequence-deprecated/src/packageVersion.ts | 2 +- experimental/dds/tree/package.json | 2 +- experimental/dds/tree2/package.json | 2 +- experimental/framework/data-objects/package.json | 2 +- experimental/framework/last-edited/package.json | 2 +- experimental/framework/react-inputs/package.json | 2 +- experimental/framework/tree-react-api/package.json | 2 +- lerna.json | 2 +- package.json | 2 +- packages/common/client-utils/package.json | 2 +- packages/common/container-definitions/package.json | 2 +- packages/common/core-interfaces/package.json | 2 +- packages/common/core-utils/package.json | 2 +- packages/common/driver-definitions/package.json | 2 +- packages/dds/cell/package.json | 2 +- packages/dds/cell/src/packageVersion.ts | 2 +- packages/dds/counter/package.json | 2 +- packages/dds/counter/src/packageVersion.ts | 2 +- packages/dds/ink/package.json | 2 +- packages/dds/ink/src/packageVersion.ts | 2 +- packages/dds/map/package.json | 2 +- packages/dds/map/src/packageVersion.ts | 2 +- packages/dds/matrix/package.json | 2 +- packages/dds/matrix/src/packageVersion.ts | 2 +- packages/dds/merge-tree/package.json | 2 +- packages/dds/ordered-collection/package.json | 2 +- packages/dds/ordered-collection/src/packageVersion.ts | 2 +- packages/dds/pact-map/package.json | 2 +- packages/dds/pact-map/src/packageVersion.ts | 2 +- packages/dds/register-collection/package.json | 2 +- packages/dds/register-collection/src/packageVersion.ts | 2 +- packages/dds/sequence/package.json | 2 +- packages/dds/sequence/src/packageVersion.ts | 2 +- packages/dds/shared-object-base/package.json | 2 +- packages/dds/shared-object-base/src/packageVersion.ts | 2 +- packages/dds/shared-summary-block/package.json | 2 +- packages/dds/shared-summary-block/src/packageVersion.ts | 2 +- packages/dds/task-manager/package.json | 2 +- packages/dds/task-manager/src/packageVersion.ts | 2 +- packages/dds/test-dds-utils/package.json | 2 +- packages/drivers/debugger/package.json | 2 +- packages/drivers/driver-base/package.json | 2 +- packages/drivers/driver-base/src/packageVersion.ts | 2 +- packages/drivers/driver-web-cache/package.json | 2 +- packages/drivers/driver-web-cache/src/packageVersion.ts | 2 +- packages/drivers/file-driver/package.json | 2 +- packages/drivers/fluidapp-odsp-urlResolver/package.json | 2 +- packages/drivers/local-driver/package.json | 2 +- packages/drivers/odsp-driver-definitions/package.json | 2 +- packages/drivers/odsp-driver/package.json | 2 +- packages/drivers/odsp-driver/src/packageVersion.ts | 2 +- packages/drivers/odsp-urlResolver/package.json | 2 +- packages/drivers/replay-driver/package.json | 2 +- packages/drivers/routerlicious-driver/package.json | 2 +- packages/drivers/routerlicious-driver/src/packageVersion.ts | 2 +- packages/drivers/routerlicious-urlResolver/package.json | 2 +- packages/drivers/tinylicious-driver/package.json | 2 +- packages/framework/agent-scheduler/package.json | 2 +- packages/framework/aqueduct/package.json | 2 +- packages/framework/attributor/package.json | 2 +- .../framework/client-logger/app-insights-logger/package.json | 2 +- packages/framework/data-object-base/package.json | 2 +- packages/framework/dds-interceptions/package.json | 2 +- packages/framework/fluid-framework/package.json | 2 +- packages/framework/fluid-static/package.json | 2 +- packages/framework/oldest-client-observer/package.json | 2 +- packages/framework/request-handler/package.json | 2 +- packages/framework/synthesize/package.json | 2 +- packages/framework/tinylicious-client/package.json | 2 +- packages/framework/undo-redo/package.json | 2 +- packages/framework/view-adapters/package.json | 2 +- packages/framework/view-interfaces/package.json | 2 +- packages/loader/container-loader/package.json | 2 +- packages/loader/container-loader/src/packageVersion.ts | 2 +- packages/loader/driver-utils/package.json | 2 +- packages/loader/driver-utils/src/packageVersion.ts | 2 +- packages/loader/location-redirection-utils/package.json | 2 +- packages/loader/test-loader-utils/package.json | 2 +- packages/runtime/container-runtime-definitions/package.json | 2 +- packages/runtime/container-runtime/package.json | 2 +- packages/runtime/container-runtime/src/packageVersion.ts | 2 +- packages/runtime/datastore-definitions/package.json | 2 +- packages/runtime/datastore/package.json | 2 +- packages/runtime/runtime-definitions/package.json | 2 +- packages/runtime/runtime-utils/package.json | 2 +- packages/runtime/test-runtime-utils/package.json | 2 +- packages/test/functional-tests/package.json | 2 +- packages/test/local-server-tests/package.json | 2 +- packages/test/mocha-test-setup/package.json | 2 +- packages/test/mocha-test-setup/src/packageVersion.ts | 2 +- packages/test/snapshots/package.json | 2 +- packages/test/snapshots/src/packageVersion.ts | 2 +- packages/test/stochastic-test-utils/package.json | 2 +- packages/test/test-app-insights-logger/package.json | 2 +- packages/test/test-driver-definitions/package.json | 2 +- packages/test/test-drivers/package.json | 2 +- packages/test/test-drivers/src/packageVersion.ts | 2 +- packages/test/test-end-to-end-tests/package.json | 2 +- packages/test/test-end-to-end-tests/src/packageVersion.ts | 2 +- packages/test/test-pairwise-generator/package.json | 2 +- packages/test/test-service-load/package.json | 2 +- packages/test/test-service-load/src/packageVersion.ts | 2 +- packages/test/test-utils/package.json | 2 +- packages/test/test-utils/src/packageVersion.ts | 2 +- packages/test/test-version-utils/package.json | 2 +- packages/test/test-version-utils/src/packageVersion.ts | 2 +- packages/tools/devtools/devtools-browser-extension/package.json | 2 +- packages/tools/devtools/devtools-core/package.json | 2 +- packages/tools/devtools/devtools-core/src/packageVersion.ts | 2 +- packages/tools/devtools/devtools-example/package.json | 2 +- packages/tools/devtools/devtools-view/package.json | 2 +- packages/tools/devtools/devtools/package.json | 2 +- packages/tools/fetch-tool/package.json | 2 +- packages/tools/fluid-runner/package.json | 2 +- packages/tools/replay-tool/package.json | 2 +- packages/tools/webpack-fluid-loader/package.json | 2 +- packages/utils/odsp-doclib-utils/package.json | 2 +- packages/utils/odsp-doclib-utils/src/packageVersion.ts | 2 +- packages/utils/telemetry-utils/package.json | 2 +- packages/utils/tool-utils/package.json | 2 +- packages/utils/tool-utils/src/packageVersion.ts | 2 +- 192 files changed, 192 insertions(+), 192 deletions(-) diff --git a/azure/packages/azure-client/package.json b/azure/packages/azure-client/package.json index 7feb4fe77f02..0ea94082cf46 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.1.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": { diff --git a/azure/packages/azure-local-service/package.json b/azure/packages/azure-local-service/package.json index a97a1d6fdd9a..0c2df2cb394f 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.1.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 27940f84bcfc..97952d127365 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.1.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 a391331c55c3..aede157d4af6 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.1.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/test/end-to-end-tests/package.json b/azure/packages/test/end-to-end-tests/package.json index 255054035dd8..f1152158b77b 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.1.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 13dcb8f40375..d398df012325 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.1.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 550a23d8cf46..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/examples/apps/attributable-map/package.json b/examples/apps/attributable-map/package.json index cfde25220a7b..50980605968c 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.1.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 19ed32641920..01f7143205c3 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.1.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 e7ef23dbf1f1..adf032366788 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.1.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 019454dc6e66..f45d8a57ef51 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.1.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/presence-tracker/package.json b/examples/apps/presence-tracker/package.json index 2c236e04f6a1..78c984ee5eb0 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.1.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 03421e18d196..ea42e002e214 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.1.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/benchmarks/bubblebench/baseline/package.json b/examples/benchmarks/bubblebench/baseline/package.json index bf9c61d1bcb1..d071200ce593 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.1.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 7a6a3d34c832..ec7468805c72 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.1.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/editable-shared-tree/package.json b/examples/benchmarks/bubblebench/editable-shared-tree/package.json index 9b5406b5b2ee..fd9c6ed943ef 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.1.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 3a91158c4eae..3a7ce9bb39f5 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.1.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/package.json b/examples/benchmarks/bubblebench/sharedtree/package.json index 559f5041543b..fd7c36c060c0 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.1.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/odspsnapshotfetch-perftestapp/package.json b/examples/benchmarks/odspsnapshotfetch-perftestapp/package.json index deb9b5642a13..e8c8f243ee5a 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.1.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 127c3aab1509..78a4f5377b2c 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.1.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 a81da684e18b..8c66eecfc98b 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.1.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 bc7411641979..1e9b74e3e9c6 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.1.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 25e3491685bf..fa2c9abdc57e 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.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/diceroller/package.json b/examples/data-objects/diceroller/package.json index ef0d45356725..48b300bac440 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.1.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 d9b5931bdd03..02f54efc79cb 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.1.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 6fddf1949b77..ccda1945fbb7 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.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Monaco code editor", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/multiview/constellation-model/package.json b/examples/data-objects/multiview/constellation-model/package.json index ef3a8ef8e141..a2fa195a03c1 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.1.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 52f39a6c44e9..ff5a16022bf2 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.1.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 8ed2ca1c8011..25569eeac0fd 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.1.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 0d996ff03480..1ce989639edd 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.1.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 8067cd2a66a2..6e5646258ab7 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.1.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 393713c901ff..98d1291dc4fd 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.1.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 b20b8c004b28..b85e07076398 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.1.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 f20acd6106c5..33cb4fe29589 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.1.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 a252d8e55e84..a5f28e6bcce2 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.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "ProseMirror", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/shared-text/package.json b/examples/data-objects/shared-text/package.json index a6763ce2ec45..948d1ccc427d 100644 --- a/examples/data-objects/shared-text/package.json +++ b/examples/data-objects/shared-text/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-example/shared-text", - "version": "2.0.0-internal.7.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Shared text", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/smde/package.json b/examples/data-objects/smde/package.json index 2214d6b6a304..3b2f238ca919 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.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Simple markdown editor", "homepage": "https://fluidframework.com", diff --git a/examples/data-objects/table-document/package.json b/examples/data-objects/table-document/package.json index 14c7e988fc35..bfd389a8709e 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Chaincode component containing a table's data", "homepage": "https://fluidframework.com", "repository": { diff --git a/examples/data-objects/table-view/package.json b/examples/data-objects/table-view/package.json index ef07d4547f44..60eaa3d1fe37 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.1.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", diff --git a/examples/data-objects/todo/package.json b/examples/data-objects/todo/package.json index d68959428c17..55056e000fd3 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.1.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 b8f3777ac75d..aa0612f5641d 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.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Collaborative markdown editor.", "homepage": "https://fluidframework.com", diff --git a/examples/external-data/package.json b/examples/external-data/package.json index 4d36e420e962..8a6f4d38f11c 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.1.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 957bb11395d6..ce45fedee0da 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.1.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/package.json b/examples/utils/example-utils/package.json index a97c41b93bc3..f77683156de2 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.1.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/version-migration/live-schema-upgrade/package.json b/examples/version-migration/live-schema-upgrade/package.json index 561d4927bc3f..8dbb394489e0 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.1.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 921c8a5268cc..476732141ab8 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.1.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 6708539b3113..8ad23ba2cc6d 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.1.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/view-integration/container-views/package.json b/examples/view-integration/container-views/package.json index 1ed975784a4b..12f63160542e 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.1.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 4d88bf5961ec..609094373f86 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.1.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 a6e598ca3a3c..8fdd4dbba1b8 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.1.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 93ac0e09ba0b..1a882b742a93 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.1.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 88e8a4cf9299..17ffb7ce5a87 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.1.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 4d7e4a43c1a0..a0d6f00899e9 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.1.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 981f2a4c3f45..318fe6a1883f 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.1.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 fc99f1dee8af..290604b11dbe 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.1.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 30f1236184ec..5a01173205ba 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.1.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 a3a5db1a6805..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.1.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 4cf0c856f783..ee80a5409817 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.1.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 52e5223efe7a..9ee161f318f4 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.1.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 643bdb4310c6..b18a11bd6ad1 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.1.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 7ac276586d05..e3c764a67cfd 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.1.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 fd3afeaa260f..9487537a00f3 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.1.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 d13fd526fe9b..506490848d06 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.1.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 d2f3671dc2b6..3d2efe6488b3 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.1.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 8e21264a7fbb..eb7a4b72bd5c 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.1.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 e4ceb7871b7f..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.1.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 02a36d579f21..4282b2a882ca 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.1.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 a0954ddcf061..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/ot/sharejs/json1/package.json b/experimental/dds/ot/sharejs/json1/package.json index a267fd84bcc5..8d9293540f26 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.1.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/packageVersion.ts b/experimental/dds/ot/sharejs/json1/src/packageVersion.ts index 190fb0a42a67..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/sequence-deprecated/package.json b/experimental/dds/sequence-deprecated/package.json index 92b71ce4cd3d..9e21d1927f15 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.1.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 62e530046914..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/experimental/dds/tree/package.json b/experimental/dds/tree/package.json index 23d8dd53d76f..9ffda81b8ff1 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed tree", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/dds/tree2/package.json b/experimental/dds/tree2/package.json index 692ebc4cabd5..b77b09eab5f8 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Tree", "homepage": "https://fluidframework.com", "repository": { diff --git a/experimental/framework/data-objects/package.json b/experimental/framework/data-objects/package.json index ef497984f4c3..0e7c86ce0e28 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.1.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/last-edited/package.json b/experimental/framework/last-edited/package.json index 65408553816c..afa0476c5997 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.1.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 03a19e25d5ed..f454358b67ae 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.1.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 e5677c391f25..23bbc9d4a15c 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Experimental SharedTree API for React integration.", "homepage": "https://fluidframework.com", "repository": { diff --git a/lerna.json b/lerna.json index 9fb86d4b2c8c..87b9c2f0f146 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.0.0-internal.7.1.0", + "version": "2.0.0-internal.8.0.0", "npmClient": "pnpm", "useWorkspaces": true } diff --git a/package.json b/package.json index ccba0d0d34af..3ab301acc1dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "2.0.0-internal.7.1.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 c4d908e83835..8f1d6ce71b5e 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.1.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/package.json b/packages/common/container-definitions/package.json index dca7d6ef8097..8fc3bf9535d6 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container definitions", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/common/core-interfaces/package.json b/packages/common/core-interfaces/package.json index 0b49db5322e6..5ee195dccd80 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid object interfaces", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/common/core-utils/package.json b/packages/common/core-utils/package.json index 10c5076be222..792fbb8e9540 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.1.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/driver-definitions/package.json b/packages/common/driver-definitions/package.json index 50ca568ed9b1..9f1a18f3c19b 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.1.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 cd8c66cce239..7f76867c5764 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.1.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 e9381fcb0364..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.1.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 5d205870c1d9..8c1da55c70a4 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.1.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 ba348d215081..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.1.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 8cb8bad654be..f680d7577a6f 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.1.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 42e27eea1c8e..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.1.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 7eab39d0ff75..372a49ba9599 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.1.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 da09c881248d..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/matrix/package.json b/packages/dds/matrix/package.json index e49c61972bc5..bed6ea17c45f 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed matrix", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/matrix/src/packageVersion.ts b/packages/dds/matrix/src/packageVersion.ts index 8c5d85b7a586..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index 9382a775b53f..43c8bfd6b9b4 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Merge tree", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/ordered-collection/package.json b/packages/dds/ordered-collection/package.json index b03bbfe11536..575d1fa19d88 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.1.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 fba17729bfc0..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.1.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 12ffc778918b..6e62c49c4119 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.1.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 0b1159320861..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.1.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 a06381fbb181..a108cc6906f1 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.1.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 45e98dee9051..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index 71aa4b5171e5..c59d5b1ba3a4 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Distributed sequence", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/dds/sequence/src/packageVersion.ts b/packages/dds/sequence/src/packageVersion.ts index 2aebba221e86..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/shared-object-base/package.json b/packages/dds/shared-object-base/package.json index 76773cee7a18..4655e97e498f 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.1.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 580e2e9dee02..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/shared-summary-block/package.json b/packages/dds/shared-summary-block/package.json index bb82d2ee0647..e71054fab12b 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.1.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/packageVersion.ts b/packages/dds/shared-summary-block/src/packageVersion.ts index 840215af5a28..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/dds/task-manager/package.json b/packages/dds/task-manager/package.json index 5b07a5402675..10abcb6b8b94 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.1.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 2b403fadbbbb..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.1.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 89c183e913f2..490fb7287405 100644 --- a/packages/dds/test-dds-utils/package.json +++ b/packages/dds/test-dds-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-dds-utils", - "version": "2.0.0-internal.7.1.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 1f78174432b4..5a1e8b9a2bfb 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.1.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/driver-base/package.json b/packages/drivers/driver-base/package.json index 4e6697ec431a..92e5c8448066 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.1.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 f1af970e2bec..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.1.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 e17725f1cad4..8a9bf4786f84 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.1.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 62a192750aad..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.1.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 577ec1efd60d..ee4b8992aaa1 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.1.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 0171b18916e1..c6a520f4c7bc 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.1.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/package.json b/packages/drivers/local-driver/package.json index a9dc5ff0bee5..d9bffcb3e1a6 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid local driver", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/drivers/odsp-driver-definitions/package.json b/packages/drivers/odsp-driver-definitions/package.json index e43804d774e0..60392b537bca 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.1.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 e23ab0dbba30..0f114250f0d9 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.1.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 bc8a3b054d25..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.1.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 3d02ef0fe68c..e0954e80b04f 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.1.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 c4f868f52ed1..fa9f58761695 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.1.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 523dc58efb95..4a785ff183da 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.1.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 d70cf1e02475..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.1.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 48bb18c41b5d..38fe2b2d3643 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.1.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 e8e6f9584dc5..17345f7b06a1 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.1.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 00e8ff022c69..9db0e7ae86a4 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.1.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/package.json b/packages/framework/aqueduct/package.json index f1c08d49d1b9..d11bffe3ba72 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "A set of implementations for Fluid Framework interfaces.", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/attributor/package.json b/packages/framework/attributor/package.json index f8ec41c64351..56ea072ecbaa 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Operation attributor", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/client-logger/app-insights-logger/package.json b/packages/framework/client-logger/app-insights-logger/package.json index 9379535deb89..7fd7ef8636f7 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-internal/app-insights-logger", - "version": "2.0.0-internal.7.1.0", + "version": "2.0.0-internal.8.0.0", "private": true, "description": "Contains a Fluid logging client that sends telemetry events to Azure App Insights", "homepage": "https://fluidframework.com", diff --git a/packages/framework/data-object-base/package.json b/packages/framework/data-object-base/package.json index b63952c3d372..6ad53328faac 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.1.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": { diff --git a/packages/framework/dds-interceptions/package.json b/packages/framework/dds-interceptions/package.json index 55634271ec1e..128a6d316ffc 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.1.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/fluid-framework/package.json b/packages/framework/fluid-framework/package.json index 39b862d11aa3..d3ea62619c55 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.1.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-static/package.json b/packages/framework/fluid-static/package.json index 55ac8f3cc9f4..ae665fd468ab 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.1.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": { diff --git a/packages/framework/oldest-client-observer/package.json b/packages/framework/oldest-client-observer/package.json index 1d0ba924e1ac..56183501856e 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.1.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/package.json b/packages/framework/request-handler/package.json index 072434550420..31255da8aed9 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "A simple request handling library for Fluid Framework", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/synthesize/package.json b/packages/framework/synthesize/package.json index ce4e86263d1f..1a02f6b745a4 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.1.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/tinylicious-client/package.json b/packages/framework/tinylicious-client/package.json index f857451d9133..9ad410c960e8 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.1.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": { diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index 5133e1e4e620..50236d910690 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Undo Redo", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/framework/view-adapters/package.json b/packages/framework/view-adapters/package.json index c1d5aa5a98b1..72846f24913b 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.1.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 78a02d0abb1c..7d47b6541b42 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.1.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/package.json b/packages/loader/container-loader/package.json index b8d430d94e2a..4e8d70a2fe00 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container loader", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/loader/container-loader/src/packageVersion.ts b/packages/loader/container-loader/src/packageVersion.ts index 46e984f89a51..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/loader/driver-utils/package.json b/packages/loader/driver-utils/package.json index fad4839b8cd4..3b306c062dad 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.1.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 bb8084fa10ff..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/loader/location-redirection-utils/package.json b/packages/loader/location-redirection-utils/package.json index f45738923658..91bb93751ec4 100644 --- a/packages/loader/location-redirection-utils/package.json +++ b/packages/loader/location-redirection-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/location-redirection-utils", - "version": "2.0.0-internal.7.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Location Redirection Handling Utilities", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/loader/test-loader-utils/package.json b/packages/loader/test-loader-utils/package.json index 62d6f43feda7..43fc6334d194 100644 --- a/packages/loader/test-loader-utils/package.json +++ b/packages/loader/test-loader-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-loader-utils", - "version": "2.0.0-internal.7.1.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/package.json b/packages/runtime/container-runtime-definitions/package.json index df98dcfe718f..776b1d6ebf0c 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/container-runtime/package.json b/packages/runtime/container-runtime/package.json index b560cf913f3a..3fff47549bc4 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid container runtime", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/container-runtime/src/packageVersion.ts b/packages/runtime/container-runtime/src/packageVersion.ts index 8e17d9e7601b..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/runtime/datastore-definitions/package.json b/packages/runtime/datastore-definitions/package.json index c66ac9e2e3e6..5ab42e3a3320 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid data store definitions", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/datastore/package.json b/packages/runtime/datastore/package.json index 4de81f510e72..bf70cc757e7f 100644 --- a/packages/runtime/datastore/package.json +++ b/packages/runtime/datastore/package.json @@ -1,6 +1,6 @@ { "name": "@fluidframework/datastore", - "version": "2.0.0-internal.7.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid data store implementation", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index f6d5df16e27c..0802733a0c79 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Runtime definitions", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/runtime-utils/package.json b/packages/runtime/runtime-utils/package.json index 24df758b7ada..71222e48198f 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Collection of utility functions for Fluid Runtime", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/runtime/test-runtime-utils/package.json b/packages/runtime/test-runtime-utils/package.json index 5d1bc75cfb32..eb4b4f769a9f 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid runtime test utilities", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/functional-tests/package.json b/packages/test/functional-tests/package.json index be7ed000c058..f2823c795722 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.1.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 9cb151379e82..742c7157356a 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.1.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/mocha-test-setup/package.json b/packages/test/mocha-test-setup/package.json index 475a28f2dc7b..8d815fab3787 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.1.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 de797c7a6f08..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.1.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 b81c2a72a319..a601d4791853 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.1.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 1b91728eb925..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.1.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 887db3c3ff35..75374aded7f2 100644 --- a/packages/test/stochastic-test-utils/package.json +++ b/packages/test/stochastic-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/stochastic-test-utils", - "version": "2.0.0-internal.7.1.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 e042e6e5c027..152d77711c74 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.1.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 72dbeeb0b31a..d2071285beb6 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.1.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 9570cfdba54c..7d102d69f88c 100644 --- a/packages/test/test-drivers/package.json +++ b/packages/test/test-drivers/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-drivers", - "version": "2.0.0-internal.7.1.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 5adbc77a7e24..d5dbb41ce32b 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-internal/test-drivers"; -export const pkgVersion = "2.0.0-internal.7.1.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 daf8d2fb034f..795d9c4d0c8d 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-internal/test-end-to-end-tests", - "version": "2.0.0-internal.7.1.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 933ea1090254..2a12a358b52c 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-internal/test-end-to-end-tests"; -export const pkgVersion = "2.0.0-internal.7.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-pairwise-generator/package.json b/packages/test/test-pairwise-generator/package.json index 01a067db4906..701fe945f068 100644 --- a/packages/test/test-pairwise-generator/package.json +++ b/packages/test/test-pairwise-generator/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-pairwise-generator", - "version": "2.0.0-internal.7.1.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 56a60ac36397..1b66a034baa5 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.1.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/packageVersion.ts b/packages/test/test-service-load/src/packageVersion.ts index 97cbfcf8be78..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-utils/package.json b/packages/test/test-utils/package.json index c7e3869f99da..8565a43edddd 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Utilities for Fluid tests", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/test/test-utils/src/packageVersion.ts b/packages/test/test-utils/src/packageVersion.ts index 6dcc239edbcc..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/test/test-version-utils/package.json b/packages/test/test-version-utils/package.json index e66b6bdfe1c3..d5a9f6c7a956 100644 --- a/packages/test/test-version-utils/package.json +++ b/packages/test/test-version-utils/package.json @@ -1,6 +1,6 @@ { "name": "@fluid-internal/test-version-utils", - "version": "2.0.0-internal.7.1.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/packageVersion.ts b/packages/test/test-version-utils/src/packageVersion.ts index e283b6025e48..1d6aef192aad 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-internal/test-version-utils"; -export const pkgVersion = "2.0.0-internal.7.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/tools/devtools/devtools-browser-extension/package.json b/packages/tools/devtools/devtools-browser-extension/package.json index 9a791fa6cb35..34020e5e958b 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.1.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/package.json b/packages/tools/devtools/devtools-core/package.json index 59bdc475bcdc..2032c54dd847 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.1.0", + "version": "2.0.0-internal.8.0.0", "description": "Fluid Framework developer tools core functionality", "homepage": "https://fluidframework.com", "repository": { diff --git a/packages/tools/devtools/devtools-core/src/packageVersion.ts b/packages/tools/devtools/devtools-core/src/packageVersion.ts index b525a7a70870..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/packages/tools/devtools/devtools-example/package.json b/packages/tools/devtools/devtools-example/package.json index ff6a54de63c3..5e4d3dec5245 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.1.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 ad43699d109a..f28edb47b7ab 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.1.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/package.json b/packages/tools/devtools/devtools/package.json index 722355f3acfb..91f16f314db2 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.1.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 f6f8e9fc28d5..e3d3d7916e1f 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.1.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 6870edbd2009..9023469a4d44 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.1.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 2d412bd1d03c..4b6228bb278a 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.1.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 9aa473f8b83a..3f7d9c07c7ee 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.1.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/utils/odsp-doclib-utils/package.json b/packages/utils/odsp-doclib-utils/package.json index e74cc9c025a1..c16a7d6b9cb7 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.1.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 32108e387ebd..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.1.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 684682659cea..6375c3eae062 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.1.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 fec65b623502..f1e8be7e0417 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.1.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 420385cd5c7f..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.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; From c426adf17ca71b1c202d9b8afdfbe9ac3415a1b2 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:47:37 -0700 Subject: [PATCH 02/50] chore(merge-tree sequence examples): remove NestBegin and NestEnd reference types (#17546) This is a ~conservative first pass at removing some of the tech debt related to the `NestBegin` and `NestEnd` reference types. Alongside these enum variants, this change removes `getStackContext`, `RangeStackMap`, the `shared-text` example, and a few other things. I will add some review comments to call out some changes (or lack thereof) to make sure they make sense once CI is green. --- .changeset/tangy-beers-pump.md | 8 + api-report/merge-tree.api.md | 21 - api-report/sequence.api.md | 5 - .../prosemirror/src/fluidBridge.ts | 44 +- .../prosemirror/src/fluidCollabManager.ts | 21 +- .../prosemirror/src/prosemirror.tsx | 4 +- .../data-objects/shared-text/.eslintrc.js | 11 - examples/data-objects/shared-text/.gitignore | 3 - examples/data-objects/shared-text/.npmignore | 6 - .../data-objects/shared-text/CHANGELOG.md | 53 - examples/data-objects/shared-text/LICENSE | 21 - examples/data-objects/shared-text/README.md | 26 - .../shared-text/jest-puppeteer.config.js | 19 - .../data-objects/shared-text/jest.config.js | 33 - .../data-objects/shared-text/package.json | 116 - .../shared-text/prettier.config.cjs | 8 - .../src/client-ui-lib/controls/README.md | 13 - .../src/client-ui-lib/controls/commandBox.tsx | 167 - .../src/client-ui-lib/controls/cursor.ts | 131 - .../src/client-ui-lib/controls/debug.ts | 8 - .../src/client-ui-lib/controls/dockPanel.ts | 79 - .../src/client-ui-lib/controls/domutils.ts | 72 - .../client-ui-lib/controls/flowContainer.ts | 54 - .../src/client-ui-lib/controls/flowView.ts | 3637 ----------------- .../src/client-ui-lib/controls/index.ts | 20 - .../src/client-ui-lib/controls/keycode.ts | 21 - .../src/client-ui-lib/controls/layout.ts | 49 - .../src/client-ui-lib/controls/title.ts | 69 - .../shared-text/src/client-ui-lib/index.ts | 13 - .../src/client-ui-lib/text/characterCodes.ts | 76 - .../src/client-ui-lib/text/index.ts | 12 - .../src/client-ui-lib/text/paragraph.ts | 597 --- .../src/client-ui-lib/text/table.ts | 980 ----- .../src/client-ui-lib/ui/README.md | 16 - .../client-ui-lib/ui/browserContainerHost.ts | 91 - .../src/client-ui-lib/ui/component.ts | 81 - .../shared-text/src/client-ui-lib/ui/debug.ts | 8 - .../src/client-ui-lib/ui/geometry/index.ts | 8 - .../src/client-ui-lib/ui/geometry/point.ts | 21 - .../client-ui-lib/ui/geometry/rectangle.ts | 304 -- .../src/client-ui-lib/ui/geometry/size.ts | 13 - .../shared-text/src/client-ui-lib/ui/index.ts | 9 - .../shared-text/src/client-ui-lib/ui/utils.ts | 11 - .../shared-text/src/dataObject.ts | 53 - .../data-objects/shared-text/src/index.ts | 19 - .../data-objects/shared-text/src/view.tsx | 90 - .../shared-text/stylesheets/map.css | 10 - .../shared-text/stylesheets/style.css | 309 -- .../shared-text/tests/sharedText.test.ts | 85 - .../data-objects/shared-text/tsconfig.json | 17 - .../shared-text/webpack.config.js | 104 - .../data-objects/shared-text/webpack.dev.js | 9 - .../data-objects/shared-text/webpack.prod.js | 9 - .../webflow/src/clipboard/paste.ts | 1 - .../webflow/src/document/index.ts | 71 +- .../webflow/src/host/webflowView.tsx | 18 - .../webflow/src/test/flowdocument.spec.ts | 114 +- packages/dds/merge-tree/package.json | 26 +- packages/dds/merge-tree/src/client.ts | 15 +- packages/dds/merge-tree/src/index.ts | 5 - packages/dds/merge-tree/src/localReference.ts | 23 +- packages/dds/merge-tree/src/mergeTree.ts | 182 +- packages/dds/merge-tree/src/mergeTreeNodes.ts | 60 +- packages/dds/merge-tree/src/ops.ts | 8 - .../dds/merge-tree/src/referencePositions.ts | 33 +- packages/dds/merge-tree/src/test/beastTest.ts | 157 +- packages/dds/merge-tree/src/test/index.ts | 6 - .../validateMergeTreePrevious.generated.ts | 81 +- packages/dds/sequence/package.json | 15 +- .../sequence/src/intervals/intervalUtils.ts | 4 - .../src/intervals/sequenceInterval.ts | 4 - packages/dds/sequence/src/sequence.ts | 8 - packages/dds/sequence/src/test/testFarm.ts | 275 +- .../validateSequencePrevious.generated.ts | 4 + packages/framework/undo-redo/package.json | 6 +- .../validateUndoRedoPrevious.generated.ts | 1 + pnpm-lock.yaml | 788 ++-- 77 files changed, 454 insertions(+), 9115 deletions(-) create mode 100644 .changeset/tangy-beers-pump.md delete mode 100644 examples/data-objects/shared-text/.eslintrc.js delete mode 100644 examples/data-objects/shared-text/.gitignore delete mode 100644 examples/data-objects/shared-text/.npmignore delete mode 100644 examples/data-objects/shared-text/CHANGELOG.md delete mode 100644 examples/data-objects/shared-text/LICENSE delete mode 100644 examples/data-objects/shared-text/README.md delete mode 100644 examples/data-objects/shared-text/jest-puppeteer.config.js delete mode 100644 examples/data-objects/shared-text/jest.config.js delete mode 100644 examples/data-objects/shared-text/package.json delete mode 100644 examples/data-objects/shared-text/prettier.config.cjs delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/README.md delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/commandBox.tsx delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/cursor.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/debug.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/dockPanel.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/domutils.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/flowContainer.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/flowView.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/index.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/keycode.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/layout.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/controls/title.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/index.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/text/characterCodes.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/text/index.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/text/paragraph.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/text/table.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/README.md delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/browserContainerHost.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/component.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/debug.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/index.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/point.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/rectangle.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/size.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/index.ts delete mode 100644 examples/data-objects/shared-text/src/client-ui-lib/ui/utils.ts delete mode 100644 examples/data-objects/shared-text/src/dataObject.ts delete mode 100644 examples/data-objects/shared-text/src/index.ts delete mode 100644 examples/data-objects/shared-text/src/view.tsx delete mode 100644 examples/data-objects/shared-text/stylesheets/map.css delete mode 100644 examples/data-objects/shared-text/stylesheets/style.css delete mode 100644 examples/data-objects/shared-text/tests/sharedText.test.ts delete mode 100644 examples/data-objects/shared-text/tsconfig.json delete mode 100644 examples/data-objects/shared-text/webpack.config.js delete mode 100644 examples/data-objects/shared-text/webpack.dev.js delete mode 100644 examples/data-objects/shared-text/webpack.prod.js 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/api-report/merge-tree.api.md b/api-report/merge-tree.api.md index a37c2a7c2937..8afe2e57f33c 100644 --- a/api-report/merge-tree.api.md +++ b/api-report/merge-tree.api.md @@ -145,8 +145,6 @@ export class Client extends TypedEventEmitter { }; // (undocumented) getShortClientId(longClientId: string): number; - // @deprecated (undocumented) - getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap; // (undocumented) insertAtReferencePositionLocal(refPos: ReferencePosition, segment: ISegment): IMergeTreeInsertMsg | undefined; // (undocumented) @@ -539,9 +537,6 @@ export interface IMergeTreeTextHelper { getText(refSeq: number, clientId: number, placeholder: string, start?: number, end?: number): string; } -// @public @deprecated (undocumented) -export function internedSpaces(n: number): string; - // @internal (undocumented) export interface IRBAugmentation { // (undocumented) @@ -860,9 +855,6 @@ export interface QProperty { key?: TKey; } -// @public @deprecated (undocumented) -export type RangeStackMap = MapLike>; - // @internal (undocumented) export const RBColor: { readonly RED: 0; @@ -957,10 +949,6 @@ export interface ReferencePosition { // @public export enum ReferenceType { - // @deprecated (undocumented) - NestBegin = 2, - // @deprecated (undocumented) - NestEnd = 4, // (undocumented) RangeBegin = 16, // (undocumented) @@ -973,18 +961,9 @@ export enum ReferenceType { Transient = 256 } -// @public @deprecated (undocumented) -export const refGetRangeLabels: (refPos: ReferencePosition) => string[] | undefined; - // @public (undocumented) export const refGetTileLabels: (refPos: ReferencePosition) => string[] | undefined; -// @public @deprecated (undocumented) -export function refHasRangeLabel(refPos: ReferencePosition, label: string): boolean; - -// @public @deprecated (undocumented) -export function refHasRangeLabels(refPos: ReferencePosition): boolean; - // @public (undocumented) export function refHasTileLabel(refPos: ReferencePosition, label: string): boolean; diff --git a/api-report/sequence.api.md b/api-report/sequence.api.md index 7e9934437d1f..c6138835b245 100644 --- a/api-report/sequence.api.md +++ b/api-report/sequence.api.md @@ -40,7 +40,6 @@ 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 { Serializable } from '@fluidframework/datastore-definitions'; @@ -305,8 +304,6 @@ export type IntervalStickiness = typeof IntervalStickiness[keyof typeof Interval // @public (undocumented) export enum IntervalType { - // @deprecated (undocumented) - Nest = 1, // (undocumented) Simple = 0, SlideOnRemove = 2, @@ -562,8 +559,6 @@ export abstract class SharedSegmentSequence extends SharedOb posAfterEnd: number | undefined; }; // @deprecated (undocumented) - getStackContext(startPos: number, rangeLabels: string[]): RangeStackMap; - // @deprecated (undocumented) groupOperation(groupOp: IMergeTreeGroupMsg): void; // @internal protected guardReentrancy: (callback: () => TRet) => TRet; diff --git a/examples/data-objects/prosemirror/src/fluidBridge.ts b/examples/data-objects/prosemirror/src/fluidBridge.ts index 8bca307a086f..53abaf10c1d5 100644 --- a/examples/data-objects/prosemirror/src/fluidBridge.ts +++ b/examples/data-objects/prosemirror/src/fluidBridge.ts @@ -483,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)); @@ -514,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)); @@ -529,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) { @@ -554,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 618cd664bef2..8913d3644044 100644 --- a/examples/data-objects/prosemirror/src/fluidCollabManager.ts +++ b/examples/data-objects/prosemirror/src/fluidCollabManager.ts @@ -6,7 +6,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { EventEmitter } from "events"; -import { assert } from "@fluidframework/core-utils"; import { ILoader } from "@fluidframework/container-definitions"; import { // eslint-disable-next-line import/no-deprecated @@ -25,12 +24,7 @@ import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { ComponentView } from "./componentView"; -import { - IProseMirrorNode, - nodeTypeKey, - ProseMirrorTransactionBuilder, - sliceToGroupOps, -} from "./fluidBridge"; +import { IProseMirrorNode, ProseMirrorTransactionBuilder, sliceToGroupOps } from "./fluidBridge"; import { schema } from "./fluidSchema"; import { FootnoteView } from "./footnoteView"; import { create as createSelection } from "./selection"; @@ -102,20 +96,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 = { diff --git a/examples/data-objects/prosemirror/src/prosemirror.tsx b/examples/data-objects/prosemirror/src/prosemirror.tsx index 4864f7f65d73..fc661a929d6a 100644 --- a/examples/data-objects/prosemirror/src/prosemirror.tsx +++ b/examples/data-objects/prosemirror/src/prosemirror.tsx @@ -57,12 +57,12 @@ function createTreeMarkerOps( return [ { - seg: { marker: { refType: ReferenceType.NestBegin }, props: beginMarkerProps }, + seg: { marker: { refType: ReferenceType.Simple }, props: beginMarkerProps }, pos1: beginMarkerPos, type: MergeTreeDeltaType.INSERT, }, { - seg: { marker: { refType: ReferenceType.NestEnd }, props: endMarkerProps }, + seg: { marker: { refType: ReferenceType.Simple }, props: endMarkerProps }, pos1: endMarkerPos, type: MergeTreeDeltaType.INSERT, }, 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 c3158aeedc9f..000000000000 --- a/examples/data-objects/shared-text/CHANGELOG.md +++ /dev/null @@ -1,53 +0,0 @@ -# @fluid-example/shared-text - -## 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 948d1ccc427d..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.8.0.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 . --ignore-path ../../../.prettierignore", - "prettier:fix": "prettier --write . --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.1.1", - "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.0", - "@fluidframework/build-tools": "^0.24.0", - "@fluidframework/test-tools": "^0.2.158186", - "@types/expect-puppeteer": "2.2.1", - "@types/jest": "29.5.3", - "@types/jest-environment-puppeteer": "2.2.0", - "@types/loader-utils": "^1", - "@types/node": "^16.18.38", - "@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.6.0", - "jest": "^29.6.2", - "jest-junit": "^10.0.0", - "jest-puppeteer": "^6.2.0", - "jsdom": "^16.7.0", - "prettier": "~2.6.2", - "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/prettier.config.cjs b/examples/data-objects/shared-text/prettier.config.cjs deleted file mode 100644 index d4870022599f..000000000000 --- a/examples/data-objects/shared-text/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/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 374480941289..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/cursor.ts +++ /dev/null @@ -1,131 +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 563fa9f75097..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/controls/flowView.ts +++ /dev/null @@ -1,3637 +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 78e72e379c69..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/text/table.ts +++ /dev/null @@ -1,980 +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/debug.ts b/examples/data-objects/shared-text/src/client-ui-lib/ui/debug.ts deleted file mode 100644 index a26f2d51305c..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/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:ui"); 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 6913b8c6edd7..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/point.ts +++ /dev/null @@ -1,21 +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 ff09700565e3..000000000000 --- a/examples/data-objects/shared-text/src/client-ui-lib/ui/geometry/rectangle.ts +++ /dev/null @@ -1,304 +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 d289908e6a8c..000000000000 --- a/examples/data-objects/shared-text/src/index.ts +++ /dev/null @@ -1,19 +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 }); - -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 3acba3a359ae..000000000000 --- a/examples/data-objects/shared-text/stylesheets/style.css +++ /dev/null @@ -1,309 +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/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 08151a215424..b2b44260a522 100644 --- a/examples/data-objects/webflow/src/document/index.ts +++ b/examples/data-objects/webflow/src/document/index.ts @@ -11,7 +11,6 @@ import { } from "@fluidframework/data-object-base"; import { createDetachedLocalReferencePosition, - createInsertSegmentOp, createRemoveRangeOp, IMergeTreeRemoveMsg, ISegment, @@ -21,13 +20,7 @@ import { PropertySet, ReferencePosition, ReferenceType, - // eslint-disable-next-line import/no-deprecated - refGetRangeLabels, refGetTileLabels, - // eslint-disable-next-line import/no-deprecated - refHasRangeLabels, - reservedMarkerIdKey, - reservedRangeLabelsKey, reservedTileLabelsKey, TextSegment, } from "@fluidframework/merge-tree"; @@ -42,7 +35,7 @@ import { SequenceDeltaEvent, } 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,32 +79,11 @@ export const getDocSegmentKind = (segment: ISegment): DocSegmentKind => { const markerType = segment.refType; switch (markerType) { case ReferenceType.Tile: - case ReferenceType.Tile | ReferenceType.NestBegin: - // eslint-disable-next-line import/no-deprecated - const hasRangeLabels = refHasRangeLabels(segment); - const kind = ( - hasRangeLabels - ? // eslint-disable-next-line import/no-deprecated - 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( - // eslint-disable-next-line import/no-deprecated - refGetRangeLabels(segment)[0] === DocSegmentKind.beginTags, - `Unknown refType '${markerType}'.`, - ); return DocSegmentKind.endTags; } } @@ -194,10 +166,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/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/test/flowdocument.spec.ts b/examples/data-objects/webflow/src/test/flowdocument.spec.ts index 34265b0aede3..bc1c16317700 100644 --- a/examples/data-objects/webflow/src/test/flowdocument.spec.ts +++ b/examples/data-objects/webflow/src/test/flowdocument.spec.ts @@ -3,120 +3,8 @@ * Licensed under the MIT License. */ -import { strict as assert } from "assert"; -import { Marker, ReferenceType } from "@fluidframework/merge-tree"; -import { requestFluidObject } from "@fluidframework/runtime-utils"; -import { ITestObjectProvider } from "@fluidframework/test-utils"; import { describeLoaderCompat } from "@fluid-internal/test-version-utils"; -import { FlowDocument } from "../document/index.js"; -import { TagName } from "../util/index.js"; describeLoaderCompat("FlowDocument", (getTestObjectProvider) => { - let doc: FlowDocument; - - let provider: ITestObjectProvider; - beforeEach(async () => { - provider = getTestObjectProvider(); - const container = await provider.createContainer(FlowDocument.getFactory()); - doc = await requestFluidObject(container, "default"); - }); - - 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/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index db035754c4ec..ee227dc342a4 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -105,6 +105,30 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "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": { + "backCompat": false + }, + "RemovedTypeAliasDeclaration_RangeStackMap": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index d463a256bbee..731677c17e64 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -62,8 +62,7 @@ import { SnapshotLegacy } from "./snapshotlegacy"; import { SnapshotLoader } from "./snapshotLoader"; import { IMergeTreeTextHelper } from "./textSegment"; import { SnapshotV1 } from "./snapshotV1"; -// eslint-disable-next-line import/no-deprecated -import { ReferencePosition, RangeStackMap, DetachedReferencePosition } from "./referencePositions"; +import { ReferencePosition, DetachedReferencePosition } from "./referencePositions"; import { MergeTree } from "./mergeTree"; import { MergeTreeTextHelper } from "./MergeTreeTextHelper"; import { walkAllChildSegments } from "./mergeTreeNodeWalk"; @@ -1053,18 +1052,6 @@ export class Client extends TypedEventEmitter { return loader.initialize(storage); } - /** - * @deprecated - this functionality is no longer supported and will be removed - */ - // eslint-disable-next-line import/no-deprecated - 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; diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index b97088f84fb7..6862b73b3847 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -70,7 +70,6 @@ export { IJSONMarkerSegment, IMarkerModifiedAction, IMergeNodeCommon, - internedSpaces, IRemovalInfo, ISegment, ISegmentAction, @@ -128,12 +127,8 @@ export { 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 c7a50c7b7307..ad169b965e10 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -10,13 +10,7 @@ import { ISegment } from "./mergeTreeNodes"; import { TrackingGroup, TrackingGroupCollection } from "./mergeTreeTracking"; import { ICombiningOp, ReferenceType } from "./ops"; import { addProperties, PropertySet } from "./properties"; -import { - refHasTileLabels, - // eslint-disable-next-line import/no-deprecated - refHasRangeLabels, - ReferencePosition, - refTypeIncludesFlag, -} from "./referencePositions"; +import { refHasTileLabels, ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; /** * Dictates the preferential direction for a {@link ReferencePosition} to slide @@ -355,8 +349,7 @@ export class LocalReferenceCollection { lref.link(this.segment, offset, atRefs.push(lref).last); - // eslint-disable-next-line import/no-deprecated - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { + if (refHasTileLabels(lref)) { this.hierRefCount++; } this.refCount++; @@ -375,8 +368,7 @@ export class LocalReferenceCollection { node?.list?.remove(node); lref.link(lref.getSegment(), lref.getOffset(), undefined); - // eslint-disable-next-line import/no-deprecated - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { + if (refHasTileLabels(lref)) { this.hierRefCount--; } this.refCount--; @@ -473,8 +465,7 @@ export class LocalReferenceCollection { for (const lref of localRefs) { assertLocalReferences(lref); lref.link(splitSeg, lref.getOffset() - offset, lref.getListNode()); - // eslint-disable-next-line import/no-deprecated - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { + if (refHasTileLabels(lref)) { this.hierRefCount--; localRefs.hierRefCount++; } @@ -512,8 +503,7 @@ export class LocalReferenceCollection { ? beforeRefs.unshift(lref)?.first : beforeRefs.insertAfter(precedingRef, lref)?.first; lref.link(this.segment, 0, precedingRef); - // eslint-disable-next-line import/no-deprecated - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { + if (refHasTileLabels(lref)) { this.hierRefCount++; } this.refCount++; @@ -546,8 +536,7 @@ export class LocalReferenceCollection { lref.callbacks?.beforeSlide?.(lref); afterRefs.push(lref); lref.link(this.segment, lastOffset, afterRefs.last); - // eslint-disable-next-line import/no-deprecated - if (refHasRangeLabels(lref) || refHasTileLabels(lref)) { + if (refHasTileLabels(lref)) { this.hierRefCount++; } this.refCount++; diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 19bf94f9f443..a6780dd80b37 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -37,8 +37,6 @@ import { IncrementalExecOp, IncrementalMapState, InsertContext, - // eslint-disable-next-line import/no-deprecated - internedSpaces, IRemovalInfo, ISegment, ISegmentAction, @@ -74,12 +72,6 @@ import { refTypeIncludesFlag, ReferencePosition, DetachedReferencePosition, - // eslint-disable-next-line import/no-deprecated - RangeStackMap, - // eslint-disable-next-line import/no-deprecated - refHasRangeLabel, - // eslint-disable-next-line import/no-deprecated - refGetRangeLabels, refGetTileLabels, refHasTileLabel, } from "./referencePositions"; @@ -140,67 +132,6 @@ interface IReferenceSearchInfo { tile?: ReferencePosition; } -interface IMarkerSearchRangeInfo { - mergeTree: MergeTree; - rangeLabels: string[]; - // eslint-disable-next-line import/no-deprecated - stacks: RangeStackMap; -} - -function applyLeafRangeMarker(marker: Marker, searchInfo: IMarkerSearchRangeInfo) { - for (const rangeLabel of searchInfo.rangeLabels) { - // eslint-disable-next-line import/no-deprecated - 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, @@ -266,41 +197,6 @@ function addTileIfNotPresent(tile: ReferencePosition, tiles: object) { } } -// eslint-disable-next-line import/no-deprecated -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. @@ -308,24 +204,14 @@ function applyRangeReference(stack: Stack, delta: ReferencePo * 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; +const hierRefTypes = ReferenceType.Tile; function addNodeReferences( mergeTree: MergeTree, node: IMergeNode, rightmostTiles: MapLike, leftmostTiles: MapLike, - // eslint-disable-next-line import/no-deprecated - 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) { @@ -340,15 +226,6 @@ function addNodeReferences( addTile(segment, rightmostTiles); addTileIfNotPresent(segment, leftmostTiles); } - if (segment.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - // eslint-disable-next-line import/no-deprecated - const rangeLabels = refGetRangeLabels(segment); - if (rangeLabels) { - for (const label of rangeLabels) { - updateRangeInfo(label, segment); - } - } - } } else { const baseSegment = node as BaseSegment; if ( @@ -361,19 +238,12 @@ function addNodeReferences( addTile(lref, rightmostTiles); addTileIfNotPresent(lref, leftmostTiles); } - if (lref.refType & (ReferenceType.NestBegin | ReferenceType.NestEnd)) { - // eslint-disable-next-line import/no-deprecated - for (const label of refGetRangeLabels(lref)!) { - updateRangeInfo(label, lref); - } - } } } } } } else { - const block = node; - applyStackDelta(rangeStacks, block.rangeStacks); + const block = node as IHierBlock; extend(rightmostTiles, block.rightmostTiles); extendIfUndefined(leftmostTiles, block.leftmostTiles); } @@ -393,35 +263,16 @@ function extendIfUndefined(base: MapLike, extension: MapLike | undefine class HierMergeBlock extends MergeBlock implements IHierBlock { public rightmostTiles: MapLike; public leftmostTiles: MapLike; - public rangeStacks: MapLike>; constructor(childCount: number) { super(childCount); this.rightmostTiles = createMap(); 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]; - // eslint-disable-next-line import/no-deprecated - 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; - } } /** @@ -1305,26 +1156,6 @@ export class MergeTree { 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`. @@ -2590,7 +2421,6 @@ export class MergeTree { if (hierBlock) { hierBlock.rightmostTiles = createMap(); hierBlock.leftmostTiles = createMap(); - hierBlock.rangeStacks = {}; } for (let i = 0; i < block.childCount; i++) { const child = block.children[i]; @@ -2600,13 +2430,7 @@ export class MergeTree { len += nodeLength; } if (hierBlock) { - addNodeReferences( - this, - child, - hierBlock.rightmostTiles, - hierBlock.leftmostTiles, - hierBlock.rangeStacks, - ); + addNodeReferences(this, child, hierBlock.rightmostTiles, hierBlock.leftmostTiles); } } diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 394db9670591..9fdeaa475517 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -18,15 +18,7 @@ import { ICombiningOp, IJSONSegment, IMarkerDef, MergeTreeDeltaType, ReferenceTy import { computeHierarchicalOrdinal } from "./ordinal"; import { PartialSequenceLengths } from "./partialLengths"; import { clone, createMap, MapLike, PropertySet } from "./properties"; -import { - refTypeIncludesFlag, - // eslint-disable-next-line import/no-deprecated - RangeStackMap, - ReferencePosition, - // eslint-disable-next-line import/no-deprecated - refGetRangeLabels, - refGetTileLabels, -} from "./referencePositions"; +import { refTypeIncludesFlag, ReferencePosition, refGetTileLabels } from "./referencePositions"; import { SegmentGroupCollection } from "./segmentGroupCollection"; import { PropertiesManager, PropertiesRollback } from "./segmentPropertiesManager"; @@ -89,11 +81,8 @@ export interface IMergeBlock extends IMergeNodeCommon { * @internal */ export interface IHierBlock extends IMergeBlock { - hierToString(indentCount: number): string; rightmostTiles: MapLike; leftmostTiles: MapLike; - // eslint-disable-next-line import/no-deprecated - rangeStacks: RangeStackMap; } /** @@ -725,20 +714,6 @@ export const compareNumbers = (a: number, b: number) => a - b; 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. - */ -export function internedSpaces(n: number) { - if (indentStrings[n] === undefined) { - indentStrings[n] = ""; - for (let i = 0; i < n; i++) { - indentStrings[n] += " "; - } - } - return indentStrings[n]; -} - export interface IConsensusInfo { marker: Marker; callback: (m: Marker) => void; @@ -760,18 +735,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) { @@ -788,26 +751,7 @@ export function debugMarkerToString(marker: Marker): string { lbuf += tileLabel; } } - // eslint-disable-next-line import/no-deprecated - 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/ops.ts b/packages/dds/merge-tree/src/ops.ts index b5179d96f7d8..3f68ac0f9b73 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -12,14 +12,6 @@ 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 - */ - NestBegin = 0x2, - /** - * @deprecated - this functionality is no longer supported and will be removed - */ - NestEnd = 0x4, RangeBegin = 0x10, RangeEnd = 0x20, /** diff --git a/packages/dds/merge-tree/src/referencePositions.ts b/packages/dds/merge-tree/src/referencePositions.ts index 59f7bfe7e7da..aa490a0a24a1 100644 --- a/packages/dds/merge-tree/src/referencePositions.ts +++ b/packages/dds/merge-tree/src/referencePositions.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. */ -import { Stack } from "./collections"; import { SlidingPreference } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; import { ReferenceType, ICombiningOp } from "./ops"; -import { PropertySet, MapLike } from "./properties"; +import { PropertySet } from "./properties"; export const reservedTileLabelsKey = "referenceTileLabels"; export const reservedRangeLabelsKey = "referenceRangeLabels"; @@ -26,40 +25,15 @@ 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. - */ -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; - export function refHasTileLabel(refPos: ReferencePosition, label: string): boolean { const tileLabels = refGetTileLabels(refPos); return tileLabels?.includes(label) ?? false; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - */ -export function refHasRangeLabel(refPos: ReferencePosition, label: string): boolean { - const rangeLabels = refGetRangeLabels(refPos); - return rangeLabels?.includes(label) ?? false; -} - export function refHasTileLabels(refPos: ReferencePosition): boolean { return refGetTileLabels(refPos) !== undefined; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - */ -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. @@ -104,11 +78,6 @@ export interface ReferencePosition { isLeaf(): this is ISegment; } -/** - * @deprecated This functionality is deprecated and will be removed in a future release. - */ -export type RangeStackMap = MapLike>; - export const DetachedReferencePosition = -1; export function minReferencePosition(a: T, b: T): T { diff --git a/packages/dds/merge-tree/src/test/beastTest.ts b/packages/dds/merge-tree/src/test/beastTest.ts index 8803ba64dcc2..b292f9dfd7ab 100644 --- a/packages/dds/merge-tree/src/test/beastTest.ts +++ b/packages/dds/merge-tree/src/test/beastTest.ts @@ -4,7 +4,6 @@ */ /* 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 */ @@ -22,16 +21,9 @@ import { PropertyAction, RedBlackTree, SortedDictionary, - Stack, } from "../collections"; import { LocalClientId, UnassignedSequenceNumber, UniversalSequenceNumber } from "../constants"; -import { - IJSONMarkerSegment, - IMergeNode, - ISegment, - Marker, - reservedMarkerIdKey, -} from "../mergeTreeNodes"; +import { IJSONMarkerSegment, IMergeNode, ISegment, reservedMarkerIdKey } from "../mergeTreeNodes"; import { IMergeTreeDeltaOpArgs } from "../mergeTreeDeltaCallback"; import { createRemoveRangeOp } from "../opBuilder"; import { IMergeTreeOp, MergeTreeDeltaType, ReferenceType } from "../ops"; @@ -1647,10 +1639,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: @@ -1689,7 +1677,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; @@ -1703,7 +1691,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], }); @@ -1712,141 +1700,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; @@ -1964,10 +1817,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/index.ts b/packages/dds/merge-tree/src/test/index.ts index 0ee7d38bb3c2..09ed0a1cdff9 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -91,7 +91,6 @@ export { IMergeTreeRemoveMsg, IMergeTreeSegmentDelta, IMergeTreeTextHelper, - internedSpaces, IRBAugmentation, IRBMatcher, IRelativePosition, @@ -123,23 +122,18 @@ export { PropertyAction, PropertySet, QProperty, - RangeStackMap, RBColor, RBNode, RBNodeActions, RedBlackTree, ReferencePosition, ReferenceType, - refGetRangeLabels, refGetTileLabels, - refHasRangeLabel, - refHasRangeLabels, refHasTileLabel, refHasTileLabels, refTypeIncludesFlag, reservedMarkerIdKey, reservedMarkerSimpleTypeKey, - reservedRangeLabelsKey, reservedTileLabelsKey, revertMergeTreeDeltaRevertibles, SegmentAccumulator, 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 34e5f942a304..ab7f9ad8164b 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -91,6 +91,7 @@ declare function get_current_ClassDeclaration_Client(): declare function use_old_ClassDeclaration_Client( use: TypeOnly); use_old_ClassDeclaration_Client( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_Client()); /* @@ -1704,26 +1705,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); -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); -use_old_TypeAliasDeclaration_RangeStackMap( - get_current_TypeAliasDeclaration_RangeStackMap()); /* * Validate forward compat by using old type in place of current type @@ -2760,26 +2749,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); -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); -use_old_FunctionDeclaration_internedSpaces( - get_current_FunctionDeclaration_internedSpaces()); /* * Validate forward compat by using old type in place of current type @@ -2880,26 +2857,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); -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); -use_old_VariableDeclaration_refGetRangeLabels( - get_current_VariableDeclaration_refGetRangeLabels()); /* * Validate forward compat by using old type in place of current type @@ -2928,50 +2893,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); -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); -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); -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); -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/sequence/package.json b/packages/dds/sequence/package.json index 1ae98557fca8..88bfcfaeef7a 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -120,6 +120,19 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_ISharedString": { + "backCompat": false + }, + "ClassDeclaration_SharedSegmentSequence": { + "backCompat": false + }, + "ClassDeclaration_SharedSequence": { + "backCompat": false + }, + "ClassDeclaration_SharedString": { + "backCompat": false + } + } } } diff --git a/packages/dds/sequence/src/intervals/intervalUtils.ts b/packages/dds/sequence/src/intervals/intervalUtils.ts index ed942358222b..8302f90668b1 100644 --- a/packages/dds/sequence/src/intervals/intervalUtils.ts +++ b/packages/dds/sequence/src/intervals/intervalUtils.ts @@ -82,10 +82,6 @@ export const 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 diff --git a/packages/dds/sequence/src/intervals/sequenceInterval.ts b/packages/dds/sequence/src/intervals/sequenceInterval.ts index be896b39b633..cc2e18f77674 100644 --- a/packages/dds/sequence/src/intervals/sequenceInterval.ts +++ b/packages/dds/sequence/src/intervals/sequenceInterval.ts @@ -576,10 +576,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/sequence.ts b/packages/dds/sequence/src/sequence.ts index c2d016aa6982..724c9a25434c 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -32,7 +32,6 @@ import { matchProperties, MergeTreeDeltaType, PropertySet, - RangeStackMap, ReferencePosition, ReferenceType, MergeTreeRevertibleDriver, @@ -446,13 +445,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. diff --git a/packages/dds/sequence/src/test/testFarm.ts b/packages/dds/sequence/src/test/testFarm.ts index dbb26986ed04..9552fe6ca6e5 100644 --- a/packages/dds/sequence/src/test/testFarm.ts +++ b/packages/dds/sequence/src/test/testFarm.ts @@ -4,16 +4,14 @@ */ // 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"; +import { reservedRangeLabelsKey, ReferenceType } from "@fluidframework/merge-tree"; // eslint-disable-next-line import/no-internal-modules import * as MergeTree from "@fluidframework/merge-tree/dist/test/"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; @@ -29,20 +27,6 @@ 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; @@ -107,17 +91,17 @@ function makeBookmarks(client: MergeTree.TestClient, bookmarkCount: number) { const lref1 = client.createLocalReferencePosition( baseSegment1, segoff1.offset, - MergeTree.ReferenceType.RangeBegin, + ReferenceType.RangeBegin, undefined, ); const lref2 = client.createLocalReferencePosition( baseSegment2, segoff2.offset, - MergeTree.ReferenceType.RangeEnd, + ReferenceType.RangeEnd, undefined, ); - lref1.addProperties({ [MergeTree.reservedRangeLabelsKey]: ["bookmark"] }); - lref2.addProperties({ [MergeTree.reservedRangeLabelsKey]: ["bookmark"] }); + lref1.addProperties({ [reservedRangeLabelsKey]: ["bookmark"] }); + lref2.addProperties({ [reservedRangeLabelsKey]: ["bookmark"] }); bookmarks.push(new SequenceInterval(client, lref1, lref2, IntervalType.Simple)); } else { i--; @@ -142,11 +126,11 @@ function makeReferences(client: MergeTree.TestClient, referenceCount: number) { const lref = client.createLocalReferencePosition( baseSegment, segoff.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); - if (i & 1) { - lref.refType = MergeTree.ReferenceType.SlideOnRemove; + if (i % 2 === 1) { + lref.refType = ReferenceType.SlideOnRemove; } refs.push(lref); } else { @@ -369,7 +353,7 @@ export function TestPack(verbose = true) { const preLen = client.getLength(); const pos = random.integer(0, preLen)(mt); if (includeMarkers) { - const markerOp = client.insertMarkerLocal(pos, MergeTree.ReferenceType.Tile, { + const markerOp = client.insertMarkerLocal(pos, ReferenceType.Tile, { [MergeTree.reservedTileLabelsKey]: "test", }); testServer.enqueueMsg( @@ -423,7 +407,7 @@ export function TestPack(verbose = true) { client.createLocalReferencePosition( segOff.segment, segOff.offset, - MergeTree.ReferenceType.Transient, + ReferenceType.Transient, undefined, ), MergeTree.TextSegment.make(word1.text), @@ -464,74 +448,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); - // } - let extractSnapTime = 0; let extractSnapOps = 0; function finishRound(roundCount: number) { @@ -615,13 +531,13 @@ export function TestPack(verbose = true) { const lrefPos1 = testServer.createLocalReferencePosition( segoff1.segment, segoff1.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); const lrefPos2 = testServer.createLocalReferencePosition( segoff2.segment, segoff2.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); checkPosRanges[i] = new SequenceInterval( @@ -648,13 +564,13 @@ export function TestPack(verbose = true) { const lrefPos1 = testServer.createLocalReferencePosition( segoff1.segment, segoff1.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); const lrefPos2 = testServer.createLocalReferencePosition( segoff2.segment, segoff2.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); checkRangeRanges[i] = new SequenceInterval( @@ -956,7 +872,7 @@ export function TestPack(verbose = true) { cli.insertTextRemote(6, "very ", undefined, 4, 2, "2"); cli.insertMarkerRemote( 0, - { refType: MergeTree.ReferenceType.Tile }, + { refType: ReferenceType.Tile }, { [MergeTree.reservedTileLabelsKey]: ["peach"] }, 5, 0, @@ -982,7 +898,7 @@ export function TestPack(verbose = true) { const lref1 = cli.createLocalReferencePosition( segoff.segment, segoff.offset, - MergeTree.ReferenceType.Simple, + ReferenceType.Simple, undefined, ); cli.insertTextRemote(0, "yyy", undefined, 2, 0, "1"); @@ -1281,7 +1197,7 @@ export function mergeTreeCheckedTest() { ); const pos = random.integer(0, preLen)(mt); // Console.log(itree.toString()); - if (i & 1) { + if (i % 2 === 1) { if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { console.log( `mr i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( @@ -1365,7 +1281,7 @@ export function mergeTreeCheckedTest() { ); const pos = random.integer(0, preLen)(mt); // Console.log(itree.toString()); - if (i & 1) { + if (i % 2 === 1) { if (!checkMarkRemoveMergeTree(mergeTree, pos, pos + dlen, true)) { console.log( `i: ${i} preLen ${preLen} pos: ${pos} dlen: ${dlen} itree len: ${mergeTree.getLength( @@ -1450,10 +1366,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: @@ -1479,7 +1391,7 @@ export class DocumentTree { } else { let id: number | undefined; if (docNode.name === "pg") { - client.insertMarkerLocal(this.pos, MergeTree.ReferenceType.Tile, { + client.insertMarkerLocal(this.pos, ReferenceType.Tile, { [MergeTree.reservedTileLabelsKey]: [docNode.name], }); this.pos++; @@ -1490,12 +1402,13 @@ export class DocumentTree { id = this.ids[docNode.name]++; const props = { [MergeTree.reservedMarkerIdKey]: trid, - [MergeTree.reservedRangeLabelsKey]: [docNode.name], + [reservedRangeLabelsKey]: [docNode.name], }; - let behaviors = MergeTree.ReferenceType.NestBegin; + let behaviors = ReferenceType.Simple; if (docNode.name === "row") { props[MergeTree.reservedTileLabelsKey] = ["pg"]; - behaviors |= MergeTree.ReferenceType.Tile; + // eslint-disable-next-line no-bitwise + behaviors |= ReferenceType.Tile; } client.insertMarkerLocal(this.pos, behaviors, props); @@ -1507,149 +1420,15 @@ export class DocumentTree { 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, { + client.insertMarkerLocal(this.pos, ReferenceType.Simple, { [MergeTree.reservedMarkerIdKey]: etrid, - [MergeTree.reservedRangeLabelsKey]: [docNode.name], + [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; @@ -1804,10 +1583,6 @@ if (testPropCopy) { propertyCopy(); } -if (docTree) { - DocumentTree.test1(); -} - if (clientServerTest) { const ppTest = true; const testPack = TestPack(); diff --git a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index ea1c523d974b..e757babde2d3 100644 --- a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts +++ b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts @@ -427,6 +427,7 @@ declare function get_current_InterfaceDeclaration_ISharedString(): declare function use_old_InterfaceDeclaration_ISharedString( use: TypeOnly); use_old_InterfaceDeclaration_ISharedString( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ISharedString()); /* @@ -955,6 +956,7 @@ declare function get_current_ClassDeclaration_SharedSegmentSequence(): declare function use_old_ClassDeclaration_SharedSegmentSequence( use: TypeOnly>); use_old_ClassDeclaration_SharedSegmentSequence( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSegmentSequence()); /* @@ -979,6 +981,7 @@ declare function get_current_ClassDeclaration_SharedSequence(): declare function use_old_ClassDeclaration_SharedSequence( use: TypeOnly>); use_old_ClassDeclaration_SharedSequence( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSequence()); /* @@ -1003,6 +1006,7 @@ declare function get_current_ClassDeclaration_SharedString(): declare function use_old_ClassDeclaration_SharedString( use: TypeOnly); use_old_ClassDeclaration_SharedString( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedString()); /* diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index 3773d8b3f30e..f53910c0a9b0 100644 --- a/packages/framework/undo-redo/package.json +++ b/packages/framework/undo-redo/package.json @@ -92,6 +92,10 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_SharedSegmentSequenceRevertible": { + "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 3ca68c1a6e58..bbca20d37d6c 100644 --- a/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts +++ b/packages/framework/undo-redo/src/test/types/validateUndoRedoPrevious.generated.ts @@ -115,6 +115,7 @@ declare function get_current_ClassDeclaration_SharedSegmentSequenceRevertible(): declare function use_old_ClassDeclaration_SharedSegmentSequenceRevertible( use: TypeOnly); use_old_ClassDeclaration_SharedSegmentSequenceRevertible( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_SharedSegmentSequenceRevertible()); /* diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f326a389ac2c..56632ab285e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2657,123 +2657,6 @@ importers: webpack-dev-server: 4.6.0_qrfhhavll5uorjbg2snjvnp22u webpack-merge: 5.8.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.0 - '@fluidframework/build-tools': ^0.24.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': ^0.2.158186 - '@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': ^16.18.38 - '@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.1.1 - eslint: ~8.6.0 - events: ^3.1.0 - jest: ^29.6.2 - jest-junit: ^10.0.0 - jest-puppeteer: ^6.2.0 - jsdom: ^16.7.0 - prettier: ~2.6.2 - 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.0 - '@fluidframework/build-tools': 0.24.0_2wska3rv6ra5ajbmktsvyxwga4 - '@fluidframework/test-tools': 0.2.158186 - '@types/expect-puppeteer': 2.2.1 - '@types/jest': 29.5.3 - '@types/jest-environment-puppeteer': 2.2.0 - '@types/loader-utils': 1.1.6 - '@types/node': 16.18.38 - '@types/prop-types': 15.7.5 - '@types/puppeteer': 1.3.0 - '@types/react': 17.0.52 - '@types/react-dom': 17.0.18 - cross-env: 7.0.3 - css-loader: 1.0.1_webpack@5.83.0 - eslint: 8.6.0 - jest: 29.6.2_@types+node@16.18.38 - jest-junit: 10.0.0 - jest-puppeteer: 6.2.0_c74ur6dz4zjug5kosl76nqtepy - jsdom: 16.7.0 - prettier: 2.6.2 - process: 0.11.10 - puppeteer: 17.1.3 - rimraf: 4.4.1 - source-map-loader: 2.0.2_webpack@5.83.0 - style-loader: 1.3.0_webpack@5.83.0 - ts-loader: 9.4.2_2g4z7xhl5j2hjnjlc6adf2vq2m - typescript: 5.1.6 - url-loader: 2.3.0_webpack@5.83.0 - webpack: 5.83.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka - webpack-dev-server: 4.6.0_jwgjd6qujecmgjcspzyejei2g4 - webpack-merge: 5.8.0 - examples/data-objects/smde: specifiers: '@fluid-tools/webpack-fluid-loader': workspace:~ @@ -3159,26 +3042,26 @@ importers: '@fluid-internal/test-version-utils': link:../../../packages/test/test-version-utils '@fluid-tools/webpack-fluid-loader': link:../../../packages/tools/webpack-fluid-loader '@fluidframework/build-common': 2.0.0 - '@fluidframework/build-tools': 0.24.0_2wska3rv6ra5ajbmktsvyxwga4 + '@fluidframework/build-tools': 0.24.0_ezpnjx26gdvgxsqdwiz4hmz33y '@fluidframework/eslint-config-fluid': 2.1.0_of3xiiz3jzv7m7yvj4uqlzepni '@fluidframework/mocha-test-setup': link:../../../packages/test/mocha-test-setup '@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils '@fluidframework/test-utils': link:../../../packages/test/test-utils '@types/debug': 4.1.7 '@types/mocha': 9.1.1 - '@types/node': 16.18.38 + '@types/node': 16.18.53 '@types/react': 17.0.52 '@types/react-dom': 17.0.18 c8: 7.13.0 copyfiles: 2.4.1 cross-env: 7.0.3 - css-loader: 1.0.1_webpack@5.83.0 + css-loader: 1.0.1_webpack@5.88.2 eslint: 8.6.0 eslint-import-resolver-typescript: 3.5.5_wwrwxdavgu4hzdoqb37x5rvvke eslint-plugin-import: 2.25.4_av4jjatakjabmml5z5flrwsy3e - esm-loader-css: 1.0.4 - file-loader: 3.0.1_webpack@5.83.0 - html-loader: 3.1.2_webpack@5.83.0 + esm-loader-css: 1.0.5 + file-loader: 3.0.1_webpack@5.88.2 + html-loader: 3.1.2_webpack@5.88.2 jsdom: 16.7.0 jsdom-global: 3.0.2_jsdom@16.7.0 mocha: 10.2.0 @@ -3187,16 +3070,16 @@ importers: moment: 2.29.4 prettier: 2.6.2 rimraf: 4.4.1 - source-map-loader: 2.0.2_webpack@5.83.0 - style-loader: 1.3.0_webpack@5.83.0 - ts-loader: 9.4.2_2g4z7xhl5j2hjnjlc6adf2vq2m - ts-node: 10.9.1_undwa5qxrriq4xnbtioa2ez7wy + source-map-loader: 2.0.2_webpack@5.88.2 + style-loader: 1.3.0_webpack@5.88.2 + ts-loader: 9.4.2_wlox7xpecxj4rvkt6b6o7frtlu + ts-node: 10.9.1_jav6lypd77ssp2hqpat3rfaqua typescript: 5.1.6 - url-loader: 2.3.0_z7i7vubznyuoravie2w6pxnco4 - webpack: 5.83.0_webpack-cli@4.10.0 + url-loader: 2.3.0_yhrvznz4lzmlahstwk5siuwj6y + webpack: 5.88.2_webpack-cli@4.10.0 webpack-bundle-analyzer: 4.8.0 - webpack-cli: 4.10.0_jqigrrmc3k657r3b7h7a5tdkca - webpack-dev-server: 4.6.0_jwgjd6qujecmgjcspzyejei2g4 + webpack-cli: 4.10.0_3zem52gpxr4nuar5zimzzagqeu + webpack-dev-server: 4.6.0_77brtp2626b3kabiknhr6mgdzu webpack-merge: 5.8.0 examples/external-data: @@ -11599,7 +11482,7 @@ packages: '@babel/compat-data': 7.22.9 '@babel/core': 7.21.8 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.10 + browserslist: 4.21.11 lru-cache: 5.1.1 semver: 6.3.1 dev: true @@ -11613,7 +11496,7 @@ packages: '@babel/compat-data': 7.22.9 '@babel/core': 7.22.10 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.10 + browserslist: 4.21.11 lru-cache: 5.1.1 semver: 6.3.1 dev: true @@ -11705,7 +11588,7 @@ packages: '@babel/traverse': 7.22.10 debug: 4.3.4 lodash.debounce: 4.0.8 - resolve: 1.22.4 + resolve: 1.22.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -11723,7 +11606,7 @@ packages: '@babel/traverse': 7.22.10 debug: 4.3.4 lodash.debounce: 4.0.8 - resolve: 1.22.4 + resolve: 1.22.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -16920,6 +16803,53 @@ packages: - webpack-cli dev: true + /@fluidframework/build-tools/0.24.0_ezpnjx26gdvgxsqdwiz4hmz33y: + resolution: {integrity: sha512-eb/Hjfxt4x6u0T4DQtXSrLUezs2bickcN2QInWmQnZcJEt7C6LMqL30eLB8p4wfLD8auZdYFiTb1lzLxOS4L6A==} + engines: {node: '>=14.17.0'} + hasBin: true + dependencies: + '@fluid-tools/version-tools': 0.24.0 + '@fluidframework/bundle-size-tools': 0.24.0_webpack-cli@4.10.0 + '@manypkg/get-packages': 2.2.0 + '@octokit/core': 4.2.4 + '@rushstack/node-core-library': 3.60.0_@types+node@16.18.53 + async: 3.2.4 + chalk: 2.4.2 + commander: 6.2.1 + cosmiconfig: 8.3.6_typescript@5.1.6 + danger: 10.9.0_@octokit+core@4.2.4 + date-fns: 2.30.0 + debug: 4.3.4 + detect-indent: 6.1.0 + execa: 5.1.1 + find-up: 5.0.0 + fs-extra: 9.1.0 + glob: 7.2.3 + ignore: 5.2.4 + json5: 2.2.3 + lodash: 4.17.21 + lodash.isequal: 4.5.0 + lodash.merge: 4.6.2 + minimatch: 7.4.6 + replace-in-file: 6.3.5 + rimraf: 4.4.1 + semver: 7.5.4 + shelljs: 0.8.5 + sort-package-json: 1.57.0 + ts-morph: 17.0.1 + type-fest: 2.19.0 + typescript: 5.1.6 + yaml: 2.3.2 + transitivePeerDependencies: + - '@swc/core' + - '@types/node' + - encoding + - esbuild + - supports-color + - uglify-js + - webpack-cli + dev: true + /@fluidframework/build-tools/0.24.0_webpack-cli@4.10.0: resolution: {integrity: sha512-eb/Hjfxt4x6u0T4DQtXSrLUezs2bickcN2QInWmQnZcJEt7C6LMqL30eLB8p4wfLD8auZdYFiTb1lzLxOS4L6A==} engines: {node: '>=14.17.0'} @@ -20456,7 +20386,7 @@ packages: - supports-color dev: true - /@pmmmwh/react-refresh-webpack-plugin/0.5.10_bw64nqwvg33hl7aokjyon4wd5e: + /@pmmmwh/react-refresh-webpack-plugin/0.5.10_qwmpmonxbjrn34cr3lonpiscue: resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} engines: {node: '>= 10.13'} peerDependencies: @@ -20492,7 +20422,7 @@ packages: react-refresh: 0.11.0 schema-utils: 3.3.0 source-map: 0.7.4 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 webpack-dev-server: 4.6.0_qrfhhavll5uorjbg2snjvnp22u dev: true @@ -20800,7 +20730,7 @@ packages: fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 - resolve: 1.22.2 + resolve: 1.22.6 semver: 7.3.8 z-schema: 5.0.5 dev: true @@ -20858,6 +20788,24 @@ packages: z-schema: 5.0.5 dev: true + /@rushstack/node-core-library/3.60.0_@types+node@16.18.53: + resolution: {integrity: sha512-PcyrqhILvzU+65wMFybQ2VeGNnU5JzhDq2OvUi3j6jPUxyllM7b2hrRUwCuVaYboewYzIbpzXFzgxe2K7ii1nw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + dependencies: + '@types/node': 16.18.53 + colors: 1.2.5 + fs-extra: 7.0.1 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.6 + semver: 7.5.4 + z-schema: 5.0.5 + dev: true + /@rushstack/rig-package/0.5.0: resolution: {integrity: sha512-bGnOW4DWHOePDiABKy6qyqYJl9i7fKn4bRucExRVt5QzyPxuVHMl8CMmCabtoNSpXzgG3qymWOrMoa/W2PpJrw==} dependencies: @@ -21493,29 +21441,29 @@ packages: '@storybook/semver': 7.3.2 '@storybook/store': 6.5.16_sfoxds7t5ydpegc3knd667wn6m '@storybook/theming': 6.5.16_sfoxds7t5ydpegc3knd667wn6m - '@types/node': 16.18.38 - babel-loader: 8.3.0_spm6r2fjendvm5kc54vm4y2hgi + '@types/node': 16.18.53 + babel-loader: 8.3.0_zox4djqyiuykqs7qgskext5kye babel-plugin-named-exports-order: 0.0.2 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 core-js: 3.30.2 - css-loader: 5.2.7_webpack@5.87.0 - fork-ts-checker-webpack-plugin: 6.5.3_j2dgzj3kv7vcae56gfr3zzsaam + css-loader: 5.2.7_webpack@5.88.2 + fork-ts-checker-webpack-plugin: 6.5.3_yaljfkipdppotrwsdrvdg6ekqy glob: 7.2.3 glob-promise: 3.4.0_glob@7.2.3 - html-webpack-plugin: 5.5.1_webpack@5.87.0 + html-webpack-plugin: 5.5.1_webpack@5.88.2 path-browserify: 1.0.1 process: 0.11.10 react: 17.0.2 react-dom: 17.0.2_react@17.0.2 stable: 0.1.8 - style-loader: 2.0.0_webpack@5.87.0 - terser-webpack-plugin: 5.3.8_webpack@5.87.0 + style-loader: 2.0.0_webpack@5.88.2 + terser-webpack-plugin: 5.3.8_webpack@5.88.2 ts-dedent: 2.2.0 typescript: 5.1.6 util-deprecate: 1.0.2 - webpack: 5.87.0_webpack-cli@4.10.0 - webpack-dev-middleware: 4.3.0_webpack@5.87.0 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-dev-middleware: 4.3.0_webpack@5.88.2 webpack-hot-middleware: 2.25.3 webpack-virtual-modules: 0.4.6 transitivePeerDependencies: @@ -21614,7 +21562,7 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/core-client/6.5.16_bbgzpo3nehcc56hlvwzpj6fvwi: + /@storybook/core-client/6.5.16_5npx26362hckf33otlq6krx6mi: resolution: {integrity: sha512-14IRaDrVtKrQ+gNWC0wPwkCNfkZOKghYV/swCUnQX3rP99defsZK8Hc7xHIYoAiOP5+sc3sweRAxgmFiJeQ1Ig==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 17.0.2 @@ -21648,7 +21596,7 @@ packages: typescript: 5.1.6 unfetch: 4.2.0 util-deprecate: 1.0.2 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /@storybook/core-client/6.5.16_ckew2m44drzujokcqnl6nbwrlq: @@ -21844,7 +21792,7 @@ packages: - webpack-command dev: true - /@storybook/core/6.5.16_3zw3mmtrv27olaa22p575zllpu: + /@storybook/core/6.5.16_k3usztbfa4a7u4wi5ubdklcrn4: resolution: {integrity: sha512-CEF3QFTsm/VMnMKtRNr4rRdLeIkIG0g1t26WcmxTdSThNPBd8CsWzQJ7Jqu7CKiut+MU4A1LMOwbwCE5F2gmyA==} peerDependencies: '@storybook/builder-webpack5': '*' @@ -21862,13 +21810,13 @@ packages: optional: true dependencies: '@storybook/builder-webpack5': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji - '@storybook/core-client': 6.5.16_bbgzpo3nehcc56hlvwzpj6fvwi + '@storybook/core-client': 6.5.16_5npx26362hckf33otlq6krx6mi '@storybook/core-server': 6.5.16_btf6hqjgprrblcaacqjnnmag2i '@storybook/manager-webpack5': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji react: 17.0.2 react-dom: 17.0.2_react@17.0.2 typescript: 5.1.6 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 transitivePeerDependencies: - '@storybook/mdx2-csf' - bluebird @@ -22002,21 +21950,21 @@ packages: '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.21.8 '@babel/preset-react': 7.18.6_@babel+core@7.21.8 '@storybook/addons': 6.5.16_sfoxds7t5ydpegc3knd667wn6m - '@storybook/core-client': 6.5.16_bbgzpo3nehcc56hlvwzpj6fvwi + '@storybook/core-client': 6.5.16_5npx26362hckf33otlq6krx6mi '@storybook/core-common': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji '@storybook/node-logger': 6.5.16 '@storybook/theming': 6.5.16_sfoxds7t5ydpegc3knd667wn6m '@storybook/ui': 6.5.16_sfoxds7t5ydpegc3knd667wn6m - '@types/node': 16.18.38 - babel-loader: 8.3.0_spm6r2fjendvm5kc54vm4y2hgi + '@types/node': 16.18.53 + babel-loader: 8.3.0_zox4djqyiuykqs7qgskext5kye case-sensitive-paths-webpack-plugin: 2.4.0 chalk: 4.1.2 core-js: 3.30.2 - css-loader: 5.2.7_webpack@5.87.0 + css-loader: 5.2.7_webpack@5.88.2 express: 4.18.2 find-up: 5.0.0 fs-extra: 9.1.0 - html-webpack-plugin: 5.5.1_webpack@5.87.0 + html-webpack-plugin: 5.5.1_webpack@5.88.2 node-fetch: 2.6.11 process: 0.11.10 react: 17.0.2 @@ -22024,14 +21972,14 @@ packages: read-pkg-up: 7.0.1 regenerator-runtime: 0.13.11 resolve-from: 5.0.0 - style-loader: 2.0.0_webpack@5.87.0 + style-loader: 2.0.0_webpack@5.88.2 telejson: 6.0.8 - terser-webpack-plugin: 5.3.8_webpack@5.87.0 + terser-webpack-plugin: 5.3.8_webpack@5.88.2 ts-dedent: 2.2.0 typescript: 5.1.6 util-deprecate: 1.0.2 - webpack: 5.87.0_webpack-cli@4.10.0 - webpack-dev-middleware: 4.3.0_webpack@5.87.0 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-dev-middleware: 4.3.0_webpack@5.88.2 webpack-virtual-modules: 0.4.6 transitivePeerDependencies: - '@swc/core' @@ -22125,7 +22073,7 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/react-docgen-typescript-plugin/1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0_lzoc5dymzawnmbeuuundb75q2q: + /@storybook/react-docgen-typescript-plugin/1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0_wlox7xpecxj4rvkt6b6o7frtlu: resolution: {integrity: sha512-eVg3BxlOm2P+chijHBTByr90IZVUtgRW56qEOLX7xlww2NBuKrcavBlcmn+HH7GIUktquWkMPtvy6e0W0NgA5w==} peerDependencies: typescript: '>= 3.x' @@ -22139,7 +22087,7 @@ packages: react-docgen-typescript: 2.2.2_typescript@5.1.6 tslib: 2.6.2 typescript: 5.1.6 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 transitivePeerDependencies: - supports-color dev: true @@ -22175,21 +22123,21 @@ packages: '@babel/core': 7.21.8 '@babel/preset-flow': 7.21.4_@babel+core@7.21.8 '@babel/preset-react': 7.18.6_@babel+core@7.21.8 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_bw64nqwvg33hl7aokjyon4wd5e + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10_qwmpmonxbjrn34cr3lonpiscue '@storybook/addons': 6.5.16_sfoxds7t5ydpegc3knd667wn6m '@storybook/builder-webpack5': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji '@storybook/client-logger': 6.5.16 - '@storybook/core': 6.5.16_3zw3mmtrv27olaa22p575zllpu + '@storybook/core': 6.5.16_k3usztbfa4a7u4wi5ubdklcrn4 '@storybook/core-common': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji '@storybook/csf': 0.0.2--canary.4566f4d.1 '@storybook/docs-tools': 6.5.16_sfoxds7t5ydpegc3knd667wn6m '@storybook/manager-webpack5': 6.5.16_phk7hchvg6z4fkwftqogmtp2ji '@storybook/node-logger': 6.5.16 - '@storybook/react-docgen-typescript-plugin': 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0_lzoc5dymzawnmbeuuundb75q2q + '@storybook/react-docgen-typescript-plugin': 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0_wlox7xpecxj4rvkt6b6o7frtlu '@storybook/semver': 7.3.2 '@storybook/store': 6.5.16_sfoxds7t5ydpegc3knd667wn6m '@types/estree': 0.0.51 - '@types/node': 16.18.38 + '@types/node': 16.18.53 '@types/webpack-env': 1.18.0 acorn: 7.4.1 acorn-jsx: 5.3.2_acorn@7.4.1 @@ -22213,7 +22161,7 @@ packages: ts-dedent: 2.2.0 typescript: 5.1.6 util-deprecate: 1.0.2 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 transitivePeerDependencies: - '@storybook/mdx2-csf' - '@swc/core' @@ -22575,7 +22523,7 @@ packages: /@types/cheerio/0.22.31: resolution: {integrity: sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==} dependencies: - '@types/node': 16.18.38 + '@types/node': 16.18.53 dev: true /@types/chrome/0.0.232: @@ -22607,7 +22555,7 @@ packages: /@types/cors/2.8.13: resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} dependencies: - '@types/node': 16.18.40 + '@types/node': 16.18.53 /@types/d3-array/3.0.5: resolution: {integrity: sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==} @@ -22880,7 +22828,7 @@ packages: /@types/jsdom/21.1.1: resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==} dependencies: - '@types/node': 16.18.40 + '@types/node': 16.18.53 '@types/tough-cookie': 4.0.2 parse5: 7.1.2 dev: true @@ -22910,13 +22858,6 @@ packages: '@types/node': 16.18.53 dev: true - /@types/loader-utils/1.1.6: - resolution: {integrity: sha512-0U4S5kLpm3Cu9YkO46JrmujS2abL2tWsxA1SR8km6X0a1E96tfPu34zRdQSZsJ6dfRYwQpmuKmy9Mx2Od7AXag==} - dependencies: - '@types/node': 16.18.38 - '@types/webpack': 4.41.33 - dev: true - /@types/lodash.isequal/4.5.6: resolution: {integrity: sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg==} dependencies: @@ -22981,7 +22922,7 @@ packages: /@types/node-fetch/2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 16.18.38 + '@types/node': 16.18.53 form-data: 3.0.1 dev: true @@ -22990,6 +22931,7 @@ packages: /@types/node/16.18.40: resolution: {integrity: sha512-+yno3ItTEwGxXiS/75Q/aHaa5srkpnJaH+kdkTVJ3DtJEwv92itpKbxU+FjPoh2m/5G9zmUQfrL4A4C13c+iGA==} + dev: true /@types/node/16.18.53: resolution: {integrity: sha512-vVmHeo4tpF8zsknALU90Hh24VueYdu45ZlXzYWFbom61YR4avJqTFDC3QlWzjuTdAv6/3xHaxiO9NrtVZXrkmw==} @@ -23133,7 +23075,7 @@ packages: /@types/sha.js/2.4.0: resolution: {integrity: sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==} dependencies: - '@types/node': 16.18.40 + '@types/node': 16.18.53 dev: true /@types/shelljs/0.8.12: @@ -23925,6 +23867,16 @@ packages: webpack: 5.83.0_webpack-cli@4.10.0 webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka + /@webpack-cli/configtest/1.2.0_w3wu7rcwmvifygnqiqkxwjppse: + resolution: {integrity: sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==} + peerDependencies: + webpack: 4.x.x || 5.x.x || ^5.82.0 + webpack-cli: 4.x.x + dependencies: + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-cli: 4.10.0_3zem52gpxr4nuar5zimzzagqeu + dev: true + /@webpack-cli/info/1.5.0_webpack-cli@4.10.0: resolution: {integrity: sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==} peerDependencies: @@ -24867,14 +24819,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.2_debug@4.3.4 - transitivePeerDependencies: - - debug - dev: true - /axios/0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: @@ -25000,7 +24944,7 @@ packages: webpack: 5.83.0 dev: true - /babel-loader/8.3.0_spm6r2fjendvm5kc54vm4y2hgi: + /babel-loader/8.3.0_zox4djqyiuykqs7qgskext5kye: resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} peerDependencies: @@ -25012,7 +24956,7 @@ packages: loader-utils: 2.0.4 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /babel-messages/6.23.0: @@ -25080,7 +25024,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.15 cosmiconfig: 7.1.0 resolve: 1.22.6 @@ -25558,11 +25502,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'} @@ -25748,17 +25687,6 @@ packages: pako: 1.0.11 dev: true - /browserslist/4.21.10: - resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001538 - electron-to-chromium: 1.4.527 - node-releases: 2.0.13 - update-browserslist-db: 1.0.13_browserslist@4.21.10 - dev: true - /browserslist/4.21.11: resolution: {integrity: sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -27152,7 +27080,7 @@ packages: /core-js-compat/3.30.2: resolution: {integrity: sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==} dependencies: - browserslist: 4.21.10 + browserslist: 4.21.11 dev: true /core-js-pure/3.30.2: @@ -27286,8 +27214,8 @@ packages: capture-stack-trace: 1.0.2 dev: true - /create-esm-loader/0.2.3: - resolution: {integrity: sha512-cllzD6IU/mzXBs5OdQVWL3+ne5Elpu3Wdm7h5OldMbGXk76yr9XzHlQXWJ4zfs0ZAibe26rkbs4KvMAJm7fIZA==} + /create-esm-loader/0.2.5: + resolution: {integrity: sha512-WSg6l2sre6yVp0C4HYzYSJUn6H5tp7av6ZkGyiIKayR+xBqEYPrtxL+LAz8f+wD2VJs7/PDlK+SokaMkCxIGpw==} engines: {node: '>=14.x'} dependencies: semver: 7.5.4 @@ -27435,6 +27363,27 @@ packages: source-list-map: 2.0.1 webpack: 5.83.0_webpack-cli@4.10.0 + /css-loader/1.0.1_webpack@5.88.2: + resolution: {integrity: sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==} + engines: {node: '>= 6.9.0 <7.0.0 || >= 8.9.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.82.0 + dependencies: + babel-code-frame: 6.26.0 + css-selector-tokenizer: 0.7.3 + icss-utils: 2.1.0 + loader-utils: 1.4.2 + lodash: 4.17.21 + postcss: 6.0.23 + postcss-modules-extract-imports: 1.2.1 + postcss-modules-local-by-default: 1.2.0 + postcss-modules-scope: 1.1.0 + postcss-modules-values: 1.3.0 + postcss-value-parser: 3.3.1 + source-list-map: 2.0.1 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + /css-loader/3.6.0_webpack@4.47.0: resolution: {integrity: sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==} engines: {node: '>= 8.9.0'} @@ -27457,23 +27406,23 @@ packages: webpack: 4.47.0_webpack-cli@4.10.0 dev: true - /css-loader/5.2.7_webpack@5.87.0: + /css-loader/5.2.7_webpack@5.88.2: resolution: {integrity: sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==} engines: {node: '>= 10.13.0'} peerDependencies: webpack: ^4.27.0 || ^5.0.0 || ^5.82.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.23 + icss-utils: 5.1.0_postcss@8.4.28 loader-utils: 2.0.4 - postcss: 8.4.23 - postcss-modules-extract-imports: 3.0.0_postcss@8.4.23 - postcss-modules-local-by-default: 4.0.0_postcss@8.4.23 - postcss-modules-scope: 3.0.0_postcss@8.4.23 - postcss-modules-values: 4.0.0_postcss@8.4.23 + postcss: 8.4.28 + postcss-modules-extract-imports: 3.0.0_postcss@8.4.28 + postcss-modules-local-by-default: 4.0.0_postcss@8.4.28 + postcss-modules-scope: 3.0.0_postcss@8.4.28 + postcss-modules-values: 4.0.0_postcss@8.4.28 postcss-value-parser: 4.2.0 schema-utils: 3.3.0 semver: 7.5.4 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /css-select-base-adapter/0.1.1: @@ -28415,7 +28364,7 @@ packages: /dom-helpers/5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.15 csstype: 3.1.2 dev: false @@ -29135,7 +29084,7 @@ packages: dependencies: debug: 3.2.7 is-core-module: 2.13.0 - resolve: 1.22.4 + resolve: 1.22.6 transitivePeerDependencies: - supports-color dev: true @@ -29153,8 +29102,8 @@ packages: eslint-module-utils: 2.8.0_av4jjatakjabmml5z5flrwsy3e eslint-plugin-import: 2.25.4_av4jjatakjabmml5z5flrwsy3e get-tsconfig: 4.6.2 - globby: 13.1.4 - is-core-module: 2.12.1 + globby: 13.2.2 + is-core-module: 2.13.0 is-glob: 4.0.3 synckit: 0.8.5 transitivePeerDependencies: @@ -29374,11 +29323,11 @@ packages: eslint-import-resolver-node: 0.3.7 eslint-module-utils: 2.8.0_nd5yjn7vp7rjhzbqd7k4s2bww4 has: 1.0.3 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.6 - resolve: 1.22.2 + resolve: 1.22.6 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -29404,11 +29353,11 @@ packages: eslint-import-resolver-node: 0.3.7 eslint-module-utils: 2.8.0_ajv3vlqzjpoxbu6hdlpgvuycsm has: 1.0.3 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.6 - resolve: 1.22.2 + resolve: 1.22.6 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -29434,11 +29383,11 @@ packages: eslint-import-resolver-node: 0.3.7 eslint-module-utils: 2.8.0_2wwsys6gbnsdc4a7jaa5rcv4nq has: 1.0.3 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.6 - resolve: 1.22.2 + resolve: 1.22.6 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -29465,11 +29414,11 @@ packages: eslint-import-resolver-node: 0.3.7 eslint-module-utils: 2.8.0_jpibcfrp6m5v35rr33zhj2ho2y has: 1.0.3 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.6 - resolve: 1.22.2 + resolve: 1.22.6 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -29707,14 +29656,6 @@ packages: esrecurse: 4.3.0 estraverse: 4.3.0 - /eslint-scope/7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - /eslint-scope/7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -29864,7 +29805,7 @@ packages: doctrine: 3.0.0 enquirer: 2.3.6 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 + eslint-scope: 7.2.2 eslint-utils: 3.0.0_eslint@8.6.0 eslint-visitor-keys: 3.4.1 espree: 9.5.2 @@ -29897,12 +29838,12 @@ packages: - supports-color dev: true - /esm-loader-css/1.0.4: - resolution: {integrity: sha512-4tzPASNlPSd+m+LmDMxGm/2vvKKooTS2tWY+0pUTvnacN05eQE8afJZujrobSmMRUtvdys2/BjHz9mq/D2i+Ow==} + /esm-loader-css/1.0.5: + resolution: {integrity: sha512-rT++6xmS0dtJOVA0dMZjE6vEposYJTz7Gyv6nO+KD/EFtfG4O94wh1k7TlYeP5APaS8d72nW5vXJFe8Ftxj5sQ==} dependencies: - create-esm-loader: 0.2.3 + create-esm-loader: 0.2.5 npm-run-all: 4.1.5 - semver: 7.5.1 + semver: 7.5.4 dev: true /espree/6.2.1: @@ -30530,7 +30471,7 @@ packages: flat-cache: 3.0.4 dev: true - /file-loader/3.0.1_webpack@5.83.0: + /file-loader/3.0.1_webpack@5.88.2: resolution: {integrity: sha512-4sNIOXgtH/9WZq4NvlfU3Opn5ynUsqBwSLyM+I7UOwdGigTBYfVVQEwe/msZNX/j4pCJTIM14Fsw66Svo1oVrw==} engines: {node: '>= 6.9.0'} peerDependencies: @@ -30538,7 +30479,7 @@ packages: dependencies: loader-utils: 1.4.2 schema-utils: 1.0.0 - webpack: 5.83.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /file-loader/6.2.0_webpack@4.47.0: @@ -30944,7 +30885,7 @@ packages: webpack: 4.47.0_webpack-cli@4.10.0 dev: true - /fork-ts-checker-webpack-plugin/6.5.3_j2dgzj3kv7vcae56gfr3zzsaam: + /fork-ts-checker-webpack-plugin/6.5.3_yaljfkipdppotrwsdrvdg6ekqy: resolution: {integrity: sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==} engines: {node: '>=10', yarn: '>=1.0.0'} peerDependencies: @@ -30973,7 +30914,7 @@ packages: semver: 7.5.4 tapable: 1.1.3 typescript: 5.1.6 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /form-data-encoder/2.1.4: @@ -31645,7 +31586,7 @@ packages: picomatch: 2.3.1 strip-json-comments: 3.1.1 tsconfig-paths: 3.14.2 - typescript: 4.5.5 + typescript: 4.9.5 dev: true /gopd/1.0.1: @@ -32116,6 +32057,17 @@ packages: webpack: 5.83.0_webpack-cli@4.10.0 dev: true + /html-loader/3.1.2_webpack@5.88.2: + resolution: {integrity: sha512-9WQlLiAV5N9fCna4MUmBW/ifaUbuFZ2r7IZmtXzhyfyi4zgPEjXsmsYCKs+yT873MzRj+f1WMjuAiPNA7C6Tcw==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 || ^5.82.0 + dependencies: + html-minifier-terser: 6.1.0 + parse5: 6.0.1 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + /html-minifier-terser/5.1.1: resolution: {integrity: sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==} engines: {node: '>=6'} @@ -32185,7 +32137,7 @@ packages: webpack: 5.83.0_webpack-cli@4.10.0 dev: true - /html-webpack-plugin/5.5.1_webpack@5.87.0: + /html-webpack-plugin/5.5.1_webpack@5.88.2: resolution: {integrity: sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA==} engines: {node: '>=10.13.0'} peerDependencies: @@ -32196,7 +32148,7 @@ packages: lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /htmlparser2/3.10.1: @@ -32537,13 +32489,13 @@ packages: postcss: 7.0.39 dev: true - /icss-utils/5.1.0_postcss@8.4.23: + /icss-utils/5.1.0_postcss@8.4.28: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.23 + postcss: 8.4.28 dev: true /idb/6.1.5: @@ -32989,12 +32941,6 @@ packages: ci-info: 3.8.0 dev: true - /is-core-module/2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} - dependencies: - has: 1.0.3 - dev: true - /is-core-module/2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: @@ -34065,21 +34011,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.6.2: resolution: {integrity: sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -34179,19 +34110,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'} @@ -34332,19 +34250,6 @@ packages: jest-resolve: 29.6.2 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: @@ -34851,14 +34756,14 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.8.2 + acorn: 8.10.0 acorn-globals: 6.0.0 cssom: 0.4.4 cssstyle: 2.3.0 data-urls: 2.0.0 decimal.js: 10.4.3 domexception: 2.0.1 - escodegen: 2.0.0 + escodegen: 2.1.0 form-data: 3.0.1 html-encoding-sniffer: 2.0.1 http-proxy-agent: 4.0.1 @@ -34900,7 +34805,7 @@ packages: data-urls: 3.0.2 decimal.js: 10.4.3 domexception: 4.0.0 - escodegen: 2.0.0 + escodegen: 2.1.0 form-data: 4.0.0 html-encoding-sniffer: 3.0.0 http-proxy-agent: 5.0.0 @@ -38980,13 +38885,13 @@ packages: postcss: 7.0.39 dev: true - /postcss-modules-extract-imports/3.0.0_postcss@8.4.23: + /postcss-modules-extract-imports/3.0.0_postcss@8.4.28: resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.23 + postcss: 8.4.28 dev: true /postcss-modules-local-by-default/1.2.0: @@ -39005,14 +38910,14 @@ packages: postcss-value-parser: 4.2.0 dev: true - /postcss-modules-local-by-default/4.0.0_postcss@8.4.23: + /postcss-modules-local-by-default/4.0.0_postcss@8.4.28: resolution: {integrity: sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.23 - postcss: 8.4.23 + icss-utils: 5.1.0_postcss@8.4.28 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 dev: true @@ -39031,13 +38936,13 @@ packages: postcss-selector-parser: 6.0.13 dev: true - /postcss-modules-scope/3.0.0_postcss@8.4.23: + /postcss-modules-scope/3.0.0_postcss@8.4.28: resolution: {integrity: sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - postcss: 8.4.23 + postcss: 8.4.28 postcss-selector-parser: 6.0.13 dev: true @@ -39054,14 +38959,14 @@ packages: postcss: 7.0.39 dev: true - /postcss-modules-values/4.0.0_postcss@8.4.23: + /postcss-modules-values/4.0.0_postcss@8.4.28: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 dependencies: - icss-utils: 5.1.0_postcss@8.4.23 - postcss: 8.4.23 + icss-utils: 5.1.0_postcss@8.4.28 + postcss: 8.4.28 dev: true /postcss-prefix-selector/1.16.0_postcss@5.2.18: @@ -39113,15 +39018,6 @@ packages: source-map: 0.6.1 dev: true - /postcss/8.4.23: - resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - /postcss/8.4.28: resolution: {integrity: sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==} engines: {node: ^10 || ^12 || >=14} @@ -39703,7 +39599,6 @@ packages: /punycode/1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - dev: true /punycode/2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} @@ -40495,7 +40390,7 @@ packages: resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.4 + resolve: 1.22.6 /redent/1.0.0: resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==} @@ -40937,14 +40832,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /resolve/1.22.4: - resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} - hasBin: true - dependencies: - is-core-module: 2.13.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - /resolve/1.22.6: resolution: {integrity: sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==} hasBin: true @@ -42056,6 +41943,18 @@ packages: source-map-js: 0.6.2 webpack: 5.83.0_webpack-cli@4.10.0 + /source-map-loader/2.0.2_webpack@5.88.2: + resolution: {integrity: sha512-yIYkFOsKn+OdOirRJUPQpnZiMkF74raDVQjj5ni3SzbOiA57SabeX80R5zyMQAKpvKySA3Z4a85vFX3bvpC6KQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^5.0.0 || ^5.82.0 + dependencies: + abab: 2.0.6 + iconv-lite: 0.6.3 + source-map-js: 0.6.2 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + /source-map-resolve/0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -42597,18 +42496,11 @@ packages: dependencies: ansi-regex: 5.0.1 - /strip-ansi/7.0.1: - resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} - engines: {node: '>=12'} - dependencies: - ansi-regex: 6.0.1 - /strip-ansi/7.1.0: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom-buf/1.0.0: resolution: {integrity: sha512-1sUIL1jck0T1mhOLP2c696BIznzT525Lkub+n4jjMHjhjhoAQA6Ye659DxdlZBr0aLDMQoTxKIpnlqxgtwjsuQ==} @@ -42710,7 +42602,18 @@ packages: schema-utils: 2.7.1 webpack: 5.83.0_webpack-cli@4.10.0 - /style-loader/2.0.0_webpack@5.87.0: + /style-loader/1.3.0_webpack@5.88.2: + resolution: {integrity: sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==} + engines: {node: '>= 8.9.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 || ^5.82.0 + dependencies: + loader-utils: 2.0.4 + schema-utils: 2.7.1 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + + /style-loader/2.0.0_webpack@5.88.2: resolution: {integrity: sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -42718,7 +42621,7 @@ packages: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /style-to-object/0.3.0: @@ -43117,7 +43020,7 @@ packages: terser: 5.17.4 webpack: 5.83.0_webpack-cli@4.10.0 - /terser-webpack-plugin/5.3.8_webpack@5.87.0: + /terser-webpack-plugin/5.3.8_webpack@5.88.2: resolution: {integrity: sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -43138,7 +43041,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.17.4 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /terser-webpack-plugin/5.3.9_webpack@5.88.2: @@ -43590,13 +43493,28 @@ packages: webpack: ^5.0.0 || ^5.82.0 dependencies: chalk: 4.1.2 - enhanced-resolve: 5.14.0 + enhanced-resolve: 5.15.0 micromatch: 4.0.5 - semver: 7.5.1 + semver: 7.5.4 typescript: 5.1.6 webpack: 5.83.0_webpack-cli@4.10.0 dev: true + /ts-loader/9.4.2_wlox7xpecxj4rvkt6b6o7frtlu: + resolution: {integrity: sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 || ^5.82.0 + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.15.0 + micromatch: 4.0.5 + semver: 7.5.4 + typescript: 5.1.6 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + /ts-morph/17.0.1: resolution: {integrity: sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==} dependencies: @@ -43637,6 +43555,39 @@ packages: yn: 3.1.1 dev: true + /ts-node/10.9.1_jav6lypd77ssp2hqpat3rfaqua: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + '@types/node': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.53 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.1.6 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /ts-node/10.9.1_typescript@5.1.6: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -44326,17 +44277,6 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 - /update-browserslist-db/1.0.13_browserslist@4.21.10: - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.10 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - /update-browserslist-db/1.0.13_browserslist@4.21.11: resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -44414,7 +44354,7 @@ packages: webpack: 5.83.0_webpack-cli@4.10.0 dev: true - /url-loader/2.3.0_z7i7vubznyuoravie2w6pxnco4: + /url-loader/2.3.0_yhrvznz4lzmlahstwk5siuwj6y: resolution: {integrity: sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==} engines: {node: '>= 8.9.0'} peerDependencies: @@ -44424,11 +44364,11 @@ packages: file-loader: optional: true dependencies: - file-loader: 3.0.1_webpack@5.83.0 + file-loader: 3.0.1_webpack@5.88.2 loader-utils: 1.4.2 mime: 2.6.0 schema-utils: 2.7.1 - webpack: 5.83.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /url-loader/4.1.1_sd77y6q2gj67oxu7gpyhm2c5pq: @@ -44486,13 +44426,13 @@ packages: dependencies: punycode: 1.3.2 querystring: 0.2.0 + dev: false /url/0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} dependencies: punycode: 1.4.1 qs: 6.11.2 - dev: true /use-disposable/1.0.1_74b3ggvk3akyhuq4eydyx3fqim: resolution: {integrity: sha512-5Sle1XEmK3lw3xyGqeIY7UKkiUgF+TxwUty7fTsqM5D5AxfQfo2ft+LY9xKCA+W5YbaBFbOkWfQsZY/y5JhInA==} @@ -44914,20 +44854,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.9.2 - 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'} @@ -45034,7 +44960,7 @@ packages: hasBin: true dependencies: '@discoveryjs/json-ext': 0.5.7 - acorn: 8.8.2 + acorn: 8.10.0 acorn-walk: 8.2.0 chalk: 4.1.2 commander: 7.2.0 @@ -45048,7 +44974,7 @@ packages: - utf-8-validate dev: true - /webpack-cli/4.10.0_dfe3q2c2dnof5cignr4xxddwka: + /webpack-cli/4.10.0_3zem52gpxr4nuar5zimzzagqeu: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -45069,7 +44995,7 @@ packages: optional: true dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0_qrfhhavll5uorjbg2snjvnp22u + '@webpack-cli/configtest': 1.2.0_w3wu7rcwmvifygnqiqkxwjppse '@webpack-cli/info': 1.5.0_webpack-cli@4.10.0 '@webpack-cli/serve': 1.7.0_hxwqhfj3xkkgp5omnyg2m7pnsm colorette: 2.0.20 @@ -45079,11 +45005,13 @@ packages: import-local: 3.1.0 interpret: 2.2.0 rechoir: 0.7.1 - webpack: 5.83.0_webpack-cli@4.10.0 - webpack-dev-server: 4.6.0_qrfhhavll5uorjbg2snjvnp22u + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-bundle-analyzer: 4.8.0 + webpack-dev-server: 4.6.0_77brtp2626b3kabiknhr6mgdzu webpack-merge: 5.8.0 + dev: true - /webpack-cli/4.10.0_jqigrrmc3k657r3b7h7a5tdkca: + /webpack-cli/4.10.0_dfe3q2c2dnof5cignr4xxddwka: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -45115,10 +45043,8 @@ packages: interpret: 2.2.0 rechoir: 0.7.1 webpack: 5.83.0_webpack-cli@4.10.0 - webpack-bundle-analyzer: 4.8.0 - webpack-dev-server: 4.6.0_jwgjd6qujecmgjcspzyejei2g4 + webpack-dev-server: 4.6.0_qrfhhavll5uorjbg2snjvnp22u webpack-merge: 5.8.0 - dev: true /webpack-cli/4.10.0_qsn6o3u33rjjm6wv75wpbgo5ga: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} @@ -45205,7 +45131,7 @@ packages: webpack-log: 2.0.0 dev: true - /webpack-dev-middleware/4.3.0_webpack@5.87.0: + /webpack-dev-middleware/4.3.0_webpack@5.88.2: resolution: {integrity: sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w==} engines: {node: '>= v10.23.3'} peerDependencies: @@ -45217,7 +45143,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 3.3.0 - webpack: 5.87.0_webpack-cli@4.10.0 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /webpack-dev-middleware/5.3.3_webpack@5.83.0: @@ -45233,7 +45159,21 @@ packages: schema-utils: 4.0.1 webpack: 5.83.0_webpack-cli@4.10.0 - /webpack-dev-server/4.6.0_jwgjd6qujecmgjcspzyejei2g4: + /webpack-dev-middleware/5.3.3_webpack@5.88.2: + resolution: {integrity: sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 || ^5.82.0 + dependencies: + colorette: 2.0.20 + memfs: 3.5.1 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.0.1 + webpack: 5.88.2_webpack-cli@4.10.0 + dev: true + + /webpack-dev-server/4.6.0_77brtp2626b3kabiknhr6mgdzu: resolution: {integrity: sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg==} engines: {node: '>= 12.13.0'} hasBin: true @@ -45265,12 +45205,12 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - strip-ansi: 7.0.1 - url: 0.11.0 - webpack: 5.83.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka - webpack-dev-middleware: 5.3.3_webpack@5.83.0 - ws: 8.13.0 + strip-ansi: 7.1.0 + url: 0.11.3 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-cli: 4.10.0_3zem52gpxr4nuar5zimzzagqeu + webpack-dev-middleware: 5.3.3_webpack@5.88.2 + ws: 8.14.2 transitivePeerDependencies: - '@types/express' - bufferutil @@ -45311,12 +45251,12 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - strip-ansi: 7.0.1 - url: 0.11.0 + strip-ansi: 7.1.0 + url: 0.11.3 webpack: 5.83.0_webpack-cli@4.10.0 webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka webpack-dev-middleware: 5.3.3_webpack@5.83.0 - ws: 8.13.0 + ws: 8.14.2 transitivePeerDependencies: - '@types/express' - bufferutil @@ -45356,12 +45296,12 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - strip-ansi: 7.0.1 - url: 0.11.0 + strip-ansi: 7.1.0 + url: 0.11.3 webpack: 5.83.0_webpack-cli@4.10.0 webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka webpack-dev-middleware: 5.3.3_webpack@5.83.0 - ws: 8.13.0 + ws: 8.14.2 transitivePeerDependencies: - '@types/express' - bufferutil @@ -45545,47 +45485,6 @@ packages: - esbuild - uglify-js - /webpack/5.87.0_webpack-cli@4.10.0: - resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/eslint-scope': 3.7.4 - '@types/estree': 1.0.1 - '@webassemblyjs/ast': 1.11.6 - '@webassemblyjs/wasm-edit': 1.11.6 - '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.10.0 - acorn-import-assertions: 1.9.0_acorn@8.10.0 - browserslist: 4.21.11 - chrome-trace-event: 1.0.3 - enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.1 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.8_webpack@5.87.0 - watchpack: 2.4.0 - webpack-cli: 4.10.0_dfe3q2c2dnof5cignr4xxddwka - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - dev: true - /webpack/5.88.2: resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} engines: {node: '>=10.13.0'} @@ -46041,18 +45940,6 @@ packages: utf-8-validate: optional: true - /ws/8.13.0: - resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - /ws/8.14.2: resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} engines: {node: '>=10.0.0'} @@ -46064,7 +45951,6 @@ packages: optional: true utf-8-validate: optional: true - dev: true /ws/8.8.1: resolution: {integrity: sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==} From 311c1ea2864bfb774b1b0b0dacdb03c04d1f612a Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:48:55 -0700 Subject: [PATCH 03/50] chore(merge-tree sequence): misc cleanup (#17547) Misc cleanup tasks for merge-tree and sequence. See the commits for a breakdown of the changes. Potentially controversially, this PR deletes the sequence testFarm. As far as I can tell, this file is never actually executed. I am interested to know if that's not the case. --- .changeset/poor-peas-bow.md | 7 + api-report/merge-tree.api.md | 8 - packages/dds/merge-tree/package.json | 4 + .../dds/merge-tree/src/MergeTreeTextHelper.ts | 4 - .../merge-tree/src/attributionCollection.ts | 6 + packages/dds/merge-tree/src/base.ts | 1 - packages/dds/merge-tree/src/client.ts | 5 +- .../dds/merge-tree/src/collections/rbTree.ts | 12 +- packages/dds/merge-tree/src/index.ts | 1 - packages/dds/merge-tree/src/mergeTree.ts | 16 +- .../test/attributionCollection.perf.spec.ts | 1 + .../src/test/attributionCollection.spec.ts | 3 +- .../test/client.applyStashedOpFarm.spec.ts | 1 - .../src/test/client.reconnectFarm.spec.ts | 10 +- .../merge-tree/src/test/client.replay.spec.ts | 13 +- .../src/test/client.rollbackFarm.spec.ts | 6 +- packages/dds/merge-tree/src/test/index.ts | 1 - .../src/test/mergeTree.annotate.spec.ts | 26 +- ...ree.markRangeRemoved.deltaCallback.spec.ts | 3 +- .../dds/merge-tree/src/test/testClient.ts | 3 +- .../dds/merge-tree/src/test/testServer.ts | 8 +- .../validateMergeTreePrevious.generated.ts | 16 +- packages/dds/merge-tree/src/zamboni.ts | 3 +- packages/dds/sequence/package.json | 1 - packages/dds/sequence/src/intervalTree.ts | 3 - packages/dds/sequence/src/sharedString.ts | 5 +- .../dds/sequence/src/test/subSequence.spec.ts | 2 - packages/dds/sequence/src/test/testFarm.ts | 1606 ----------------- 28 files changed, 63 insertions(+), 1712 deletions(-) create mode 100644 .changeset/poor-peas-bow.md delete mode 100644 packages/dds/sequence/src/test/testFarm.ts 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/api-report/merge-tree.api.md b/api-report/merge-tree.api.md index 8afe2e57f33c..a89d35bf9eb4 100644 --- a/api-report/merge-tree.api.md +++ b/api-report/merge-tree.api.md @@ -360,14 +360,6 @@ export interface IConsensusValue { value: any; } -// @internal @deprecated -export interface IIntegerRange { - // (undocumented) - end: number; - // (undocumented) - start: number; -} - // @public (undocumented) export interface IJSONMarkerSegment extends IJSONSegment { // (undocumented) diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index ee227dc342a4..16563d6c23c5 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -106,6 +106,10 @@ }, "typeValidation": { "broken": { + "RemovedInterfaceDeclaration_IIntegerRange": { + "forwardCompat": false, + "backCompat": false + }, "RemovedFunctionDeclaration_internedSpaces": { "forwardCompat": false, "backCompat": false diff --git a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts index 5234fa9cb66c..fe10547f1212 100644 --- a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts +++ b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-deprecated import { IIntegerRange } from "./base"; import { ISegment } from "./mergeTreeNodes"; import { MergeTree } from "./mergeTree"; @@ -45,9 +44,7 @@ export class MergeTreeTextHelper implements IMergeTreeTextHelper { end: number | undefined, refSeq: number, clientId: number, - // eslint-disable-next-line import/no-deprecated ): IIntegerRange { - // eslint-disable-next-line import/no-deprecated const range: IIntegerRange = { end: end ?? this.mergeTree.getLength(refSeq, clientId), start: start ?? 0, @@ -76,7 +73,6 @@ function gatherText( } } else if (placeholder && placeholder.length > 0) { const placeholderText = - // eslint-disable-next-line @typescript-eslint/no-base-to-string placeholder === "*" ? `\n${segment}` : placeholder.repeat(segment.cachedLength); textSegment.text += placeholderText; } diff --git a/packages/dds/merge-tree/src/attributionCollection.ts b/packages/dds/merge-tree/src/attributionCollection.ts index 75b875f44590..f420381d4d3d 100644 --- a/packages/dds/merge-tree/src/attributionCollection.ts +++ b/packages/dds/merge-tree/src/attributionCollection.ts @@ -27,6 +27,7 @@ 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[]; } @@ -44,7 +45,9 @@ export interface SerializedAttributionCollection extends SequenceOffsets { * @internal */ 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; } @@ -122,7 +125,9 @@ export interface IAttributionCollection { // 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) { @@ -160,6 +165,7 @@ export class AttributionCollection implements IAttributionCollection { private getValidOpRange( op: IMergeTreeAnnotateMsg | IMergeTreeInsertMsg | IMergeTreeRemoveMsg, clientArgs: IMergeTreeClientSequenceArgs, - // eslint-disable-next-line import/no-deprecated ): IIntegerRange { let start: number | undefined = op.pos1; if (start === undefined && op.relativePos1) { @@ -599,8 +597,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, import/no-deprecated - return { start, end } as IIntegerRange; + return { start: start!, end: end! }; } /** diff --git a/packages/dds/merge-tree/src/collections/rbTree.ts b/packages/dds/merge-tree/src/collections/rbTree.ts index 801400199023..655591ea5377 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); diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index 6862b73b3847..cf99596e2a43 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 } from "./client"; export { ConflictAction, diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index a6780dd80b37..8c13f3cf8821 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -4,7 +4,6 @@ */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ /* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */ @@ -166,11 +165,12 @@ 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; } } @@ -527,7 +527,7 @@ export class MergeTree { zamboniSegments: true, }; - private static readonly theUnfinishedNode = { childCount: -1 }; + private static readonly theUnfinishedNode = { childCount: -1 } as unknown as IMergeBlock; public readonly collabWindow = new CollaborationWindow(); @@ -1194,7 +1194,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; @@ -1245,7 +1245,7 @@ export class MergeTree { foundMarker = seg; } } else { - const block = seg; + const block = seg as IHierBlock; const marker = forwards ? block.leftmostTiles[markerLabel] : block.rightmostTiles[markerLabel]; @@ -1456,7 +1456,7 @@ export class MergeTree { segments: [], localSeq, refSeq: this.collabWindow.currentSeq, - } as SegmentGroup; + } as any as SegmentGroup; if (previousProps) { _segmentGroup.previousProps = []; } 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 28bc9453a03a..8ea081d72160 100644 --- a/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts +++ b/packages/dds/merge-tree/src/test/attributionCollection.perf.spec.ts @@ -143,6 +143,7 @@ class TreeAttributionCollection implements IAttributionCollection { }); describe("serializeAttributionCollections and populateAttributionCollections round-trip", () => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const seg = (length: number): ISegment => ({ cachedLength: length } as ISegment); + const seg = (length: number): ISegment => ({ cachedLength: length } as any as ISegment); const testCases: { name: string; blob: SerializedAttributionCollection; 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 cd68f8a72c83..7f0f3b3f3d91 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.reconnectFarm.spec.ts b/packages/dds/merge-tree/src/test/client.reconnectFarm.spec.ts index dc58c847e53c..7724a8c2568f 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-internal/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.rollbackFarm.spec.ts b/packages/dds/merge-tree/src/test/client.rollbackFarm.spec.ts index 654864051232..abbbffae31f7 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/index.ts b/packages/dds/merge-tree/src/test/index.ts index 09ed0a1cdff9..17f98646b64e 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -71,7 +71,6 @@ export { ICombiningOp, IConsensusInfo, IConsensusValue, - IIntegerRange, IJSONMarkerSegment, IJSONSegment, IJSONTextSegment, 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..141d24c3fc7f 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts @@ -3,8 +3,6 @@ * 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"; @@ -231,7 +229,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 1); @@ -253,7 +251,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 0); @@ -275,7 +273,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.segmentGroups.size, 0); @@ -326,7 +324,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); const segmentInfo = mergeTree.getContainingSegment( @@ -349,7 +347,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -433,7 +431,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -449,7 +447,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -465,7 +463,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert.equal(segment.properties?.propertySource, "local2"); @@ -496,7 +494,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -631,7 +629,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); assert(segmentInfo.segment?.segmentGroups.empty); @@ -722,7 +720,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( @@ -775,7 +773,7 @@ describe("MergeTree", () => { }, sequencedMessage: { sequenceNumber: ++currentSequenceNumber, - } as ISequencedDocumentMessage, + } as any as ISequencedDocumentMessage, }); mergeTree.annotateRange( 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/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index 9fe7f8b8e9f1..8c53726f4363 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -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, ); diff --git a/packages/dds/merge-tree/src/test/testServer.ts b/packages/dds/merge-tree/src/test/testServer.ts index 9a158e61fedb..1b2e41c1fe73 100644 --- a/packages/dds/merge-tree/src/test/testServer.ts +++ b/packages/dds/merge-tree/src/test/testServer.ts @@ -57,7 +57,6 @@ export class TestServer extends TestClient { 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 { @@ -80,7 +79,6 @@ export class TestServer extends TestClient { msg.sequenceNumber = -1; } copyMsg(msg: ISequencedDocumentMessage) { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return { clientId: msg.clientId, clientSequenceNumber: msg.clientSequenceNumber, @@ -89,7 +87,7 @@ export class TestServer extends TestClient { referenceSequenceNumber: msg.referenceSequenceNumber, sequenceNumber: msg.sequenceNumber, type: msg.type, - } as ISequencedDocumentMessage; + } as any as ISequencedDocumentMessage; } private minSeq = 0; @@ -199,11 +197,7 @@ 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 console.log(client.mergeTree.toString()); return true; } 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 ab7f9ad8164b..482fa9f41e7b 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -337,26 +337,14 @@ use_old_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); -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); -use_old_InterfaceDeclaration_IIntegerRange( - get_current_InterfaceDeclaration_IIntegerRange()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index b66b00421b3d..621eb6dd05b7 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -74,8 +74,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; diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index 88bfcfaeef7a..723df7eccd2e 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -43,7 +43,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" 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/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 20f90e32444c..a96e2865a854 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -405,10 +405,7 @@ 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); + placeholder === "*" ? `\n${segment}` : placeholder.repeat(segment.cachedLength); textSegment.text += placeholderText; } else { const marker = segment as Marker; diff --git a/packages/dds/sequence/src/test/subSequence.spec.ts b/packages/dds/sequence/src/test/subSequence.spec.ts index cfc33e5c50cb..c891d30a3544 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 { diff --git a/packages/dds/sequence/src/test/testFarm.ts b/packages/dds/sequence/src/test/testFarm.ts deleted file mode 100644 index 9552fe6ca6e5..000000000000 --- a/packages/dds/sequence/src/test/testFarm.ts +++ /dev/null @@ -1,1606 +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-restricted-syntax */ -/* eslint-disable guard-for-in */ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ - -import path from "path"; -import { Trace } from "@fluid-internal/client-utils"; -import { assert } from "@fluidframework/core-utils"; -import { reservedRangeLabelsKey, ReferenceType } from "@fluidframework/merge-tree"; -// 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; -}; - -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, - ReferenceType.RangeBegin, - undefined, - ); - const lref2 = client.createLocalReferencePosition( - baseSegment2, - segoff2.offset, - ReferenceType.RangeEnd, - undefined, - ); - lref1.addProperties({ [reservedRangeLabelsKey]: ["bookmark"] }); - lref2.addProperties({ [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, - ReferenceType.Simple, - undefined, - ); - if (i % 2 === 1) { - lref.refType = 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, 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, - 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; - - 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, - ReferenceType.Simple, - undefined, - ); - const lrefPos2 = testServer.createLocalReferencePosition( - segoff2.segment, - segoff2.offset, - 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, - ReferenceType.Simple, - undefined, - ); - const lrefPos2 = testServer.createLocalReferencePosition( - segoff2.segment, - segoff2.offset, - 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: 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, - 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 % 2 === 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 % 2 === 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; - } -} - -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, 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, - [reservedRangeLabelsKey]: [docNode.name], - }; - let behaviors = ReferenceType.Simple; - if (docNode.name === "row") { - props[MergeTree.reservedTileLabelsKey] = ["pg"]; - // eslint-disable-next-line no-bitwise - behaviors |= 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, ReferenceType.Simple, { - [MergeTree.reservedMarkerIdKey]: etrid, - [reservedRangeLabelsKey]: [docNode.name], - }); - this.pos++; - } - } - } - - 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 (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; -} From 6e3697a57ce61a7d3d92dd968ccf41dc5170ceb8 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:20:37 -0700 Subject: [PATCH 04/50] chore(merge-tree): remove `MergeTree.incrementalBlockMap` (#17633) Removes `MergeTree.incrementalBlockMap`, `IncrementalMapState`, `IncrementalSegmentAction`, `IncrementalBlockAction`, and `IncrementalExecOp`. Additionally deletes dead code in `beastTest.ts`, which allows us to delete the `tst.ts` file. --- packages/dds/merge-tree/src/mergeTree.ts | 52 +--- packages/dds/merge-tree/src/mergeTreeNodes.ts | 45 --- packages/dds/merge-tree/src/test/beastTest.ts | 261 +----------------- packages/dds/merge-tree/src/test/index.ts | 1 - .../dds/merge-tree/src/test/testServer.ts | 53 +--- packages/dds/merge-tree/src/test/tst.ts | 210 -------------- 6 files changed, 10 insertions(+), 612 deletions(-) delete mode 100644 packages/dds/merge-tree/src/test/tst.ts diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 91aae77a19d7..64676f899f56 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -10,7 +10,7 @@ import { assert } from "@fluidframework/core-utils"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils"; import { IAttributionCollectionSerializer } from "./attributionCollection"; -import { Comparer, Heap, List, ListNode, Stack } from "./collections"; +import { Comparer, Heap, List, ListNode } from "./collections"; import { LocalClientId, NonCollabClient, @@ -33,8 +33,6 @@ import { IMergeBlock, IMergeLeaf, IMergeNode, - IncrementalExecOp, - IncrementalMapState, InsertContext, IRemovalInfo, ISegment, @@ -2494,54 +2492,6 @@ export class MergeTree { 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(); - } - } - } - } - private nodeMap( refSeq: number, clientId: number, diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 9fdeaa475517..512e5d21d4e6 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -275,19 +275,7 @@ export interface NodeAction { clientData: TClientData, ): boolean; } -/** - * @internal - */ -export interface IncrementalSegmentAction { - (segment: ISegment, state: IncrementalMapState); -} -/** - * @internal - */ -export interface IncrementalBlockAction { - (state: IncrementalMapState); -} /** * @internal * */ @@ -316,14 +304,6 @@ export interface SegmentActions { pre?: BlockAction; post?: BlockAction; } -/** - * @internal - */ -export interface IncrementalSegmentActions { - leaf: IncrementalSegmentAction; - pre?: IncrementalBlockAction; - post?: IncrementalBlockAction; -} /** * @internal @@ -665,31 +645,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, - ) {} -} export class CollaborationWindow { clientId = LocalClientId; diff --git a/packages/dds/merge-tree/src/test/beastTest.ts b/packages/dds/merge-tree/src/test/beastTest.ts index 6b43921d65ad..eb9d6a7aad66 100644 --- a/packages/dds/merge-tree/src/test/beastTest.ts +++ b/packages/dds/merge-tree/src/test/beastTest.ts @@ -36,7 +36,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, @@ -674,20 +673,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; @@ -704,12 +689,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; @@ -730,17 +711,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}`, ); @@ -762,8 +737,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"; @@ -790,19 +763,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) { @@ -858,12 +822,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)); @@ -909,74 +867,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) { @@ -988,15 +878,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()) { @@ -1071,11 +952,6 @@ export function TestPack(verbose = true) { randomWordMove(client); } else { randomSpateOfRemoves(client); - if (includeMarkers) { - if (client.getLength() > 200) { - randomSpateOfRemoves(client); - } - } } } if (serverProcessSome(server)) { @@ -1088,27 +964,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]); @@ -1480,12 +1344,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, @@ -1496,106 +1354,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() { @@ -1802,11 +1560,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; diff --git a/packages/dds/merge-tree/src/test/index.ts b/packages/dds/merge-tree/src/test/index.ts index 17f98646b64e..a5908008aebd 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -38,7 +38,6 @@ export { runMergeTreeOperationRunner, TestOperation, } from "./mergeTreeOperationRunner"; -export { ProxString, TST, TSTNode, TSTResult } from "./tst"; export { ClientSeq, clientSeqComparer, LRUSegment, MergeTree } from "../mergeTree"; export { MergeTreeTextHelper } from "../MergeTreeTextHelper"; export { SnapshotLegacy } from "../snapshotlegacy"; diff --git a/packages/dds/merge-tree/src/test/testServer.ts b/packages/dds/merge-tree/src/test/testServer.ts index 1b2e41c1fe73..1460adc2a13d 100644 --- a/packages/dds/merge-tree/src/test/testServer.ts +++ b/packages/dds/merge-tree/src/test/testServer.ts @@ -4,17 +4,10 @@ */ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IIntegerRange } from "../base"; -import { Heap, RedBlackTree, Stack } from "../collections"; -import { - compareNumbers, - IncrementalExecOp, - IncrementalMapState, - ISegment, -} from "../mergeTreeNodes"; +import { Heap, RedBlackTree } from "../collections"; +import { compareNumbers } from "../mergeTreeNodes"; import { ClientSeq, clientSeqComparer } from "../mergeTree"; import { PropertySet } from "../properties"; -import { TextSegment } from "../textSegment"; import { MergeTreeTextHelper } from "../MergeTreeTextHelper"; import { TestClient } from "./testClient"; @@ -138,48 +131,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; } /** 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); - } - } -} From ff48c00327ebbacd09576e165e959c1bbe05b7f4 Mon Sep 17 00:00:00 2001 From: Tyler Butler Date: Thu, 5 Oct 2023 19:41:26 -0700 Subject: [PATCH 05/50] refactor: Change some property getters to readonly fields (#17495) --- .changeset/gold-cats-tell.md | 9 +++++++++ .changeset/sixty-kings-carry.md | 9 +++++++++ api-report/local-driver.api.md | 2 +- api-report/test-runtime-utils.api.md | 4 ++-- .../local-driver/src/localDocumentStorageService.ts | 4 +--- packages/runtime/test-runtime-utils/src/mockDeltas.ts | 8 ++------ 6 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 .changeset/gold-cats-tell.md create mode 100644 .changeset/sixty-kings-carry.md 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/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/api-report/local-driver.api.md b/api-report/local-driver.api.md index b554e5d656f8..4ad0c4334273 100644 --- a/api-report/local-driver.api.md +++ b/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/api-report/test-runtime-utils.api.md b/api-report/test-runtime-utils.api.md index db82a11b8f4d..b67bf0e315bb 100644 --- a/api-report/test-runtime-utils.api.md +++ b/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) diff --git a/packages/drivers/local-driver/src/localDocumentStorageService.ts b/packages/drivers/local-driver/src/localDocumentStorageService.ts index d2d469afe562..ae3dace3374d 100644 --- a/packages/drivers/local-driver/src/localDocumentStorageService.ts +++ b/packages/drivers/local-driver/src/localDocumentStorageService.ts @@ -33,9 +33,7 @@ export class LocalDocumentStorageService implements IDocumentStorageService { protected readonly blobsShaCache = new Map(); private readonly summaryTreeUploadManager: ISummaryUploadManager; - public get repositoryUrl(): string { - return ""; - } + public readonly repositoryUrl: string = ""; constructor( private readonly id: string, diff --git a/packages/runtime/test-runtime-utils/src/mockDeltas.ts b/packages/runtime/test-runtime-utils/src/mockDeltas.ts index 57534a1a3b6c..dc8dce0a909b 100644 --- a/packages/runtime/test-runtime-utils/src/mockDeltas.ts +++ b/packages/runtime/test-runtime-utils/src/mockDeltas.ts @@ -142,17 +142,13 @@ export class MockDeltaManager return undefined as any as string; } - public get maxMessageSize(): number { - return 0; - } + public readonly maxMessageSize: number = 0; public get serviceConfiguration(): IClientConfiguration { return undefined as any as IClientConfiguration; } - public get active(): boolean { - return true; - } + public readonly active: boolean = true; public close(): void {} From 75eb656639ffee3900e4e1f191e906a84ee78fdc Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Sat, 7 Oct 2023 11:16:09 -0700 Subject: [PATCH 06/50] chore(merge-tree): remove Stack, clean up properties exports (#17644) Removes the `Stack` data structure that is now unused, and removes or marks `@internal` most exports from `properties.ts`. Additionally moves some code into test files, de-duplicates a function declaration, deletes dead code from `testServer.ts`, fixes lint warnings in matrix, and enables the `prefer-optional-chain` lint in merge-tree. --- .changeset/wet-bags-pick.md | 7 ++ api-report/merge-tree.api.md | 32 +------- .../matrix/src/test/undoRedoStackManager.ts | 1 - packages/dds/merge-tree/package.json | 24 ++++++ .../merge-tree/src/attributionCollection.ts | 1 + .../dds/merge-tree/src/collections/index.ts | 1 - .../dds/merge-tree/src/collections/stack.ts | 27 ------- packages/dds/merge-tree/src/index.ts | 5 -- packages/dds/merge-tree/src/localReference.ts | 2 - packages/dds/merge-tree/src/mergeTree.ts | 43 +--------- packages/dds/merge-tree/src/mergeTreeNodes.ts | 7 +- packages/dds/merge-tree/src/ordinal.ts | 6 -- packages/dds/merge-tree/src/properties.ts | 25 +----- packages/dds/merge-tree/src/revertibles.ts | 2 - .../src/segmentPropertiesManager.ts | 8 +- packages/dds/merge-tree/src/snapshotV1.ts | 2 - packages/dds/merge-tree/src/snapshotlegacy.ts | 2 - .../test/attributionCollection.perf.spec.ts | 1 + packages/dds/merge-tree/src/test/beastTest.ts | 1 - packages/dds/merge-tree/src/test/index.ts | 7 +- .../dds/merge-tree/src/test/ordinal.spec.ts | 8 +- .../dds/merge-tree/src/test/testServer.ts | 36 ++++----- .../validateMergeTreePrevious.generated.ts | 80 +++---------------- packages/dds/merge-tree/src/zamboni.ts | 2 - 24 files changed, 79 insertions(+), 251 deletions(-) create mode 100644 .changeset/wet-bags-pick.md delete mode 100644 packages/dds/merge-tree/src/collections/stack.ts 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/api-report/merge-tree.api.md b/api-report/merge-tree.api.md index b63d54018c63..d2425670df1e 100644 --- a/api-report/merge-tree.api.md +++ b/api-report/merge-tree.api.md @@ -15,7 +15,7 @@ import { ISummaryTreeWithStats } from '@fluidframework/runtime-definitions'; import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; -// @public @deprecated (undocumented) +// @internal (undocumented) export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet, op?: ICombiningOp, seq?: number): PropertySet; // @alpha (undocumented) @@ -189,9 +189,6 @@ export class Client extends TypedEventEmitter { walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; } -// @public @deprecated (undocumented) -export function clone(extension: MapLike | undefined): MapLike | undefined; - // @public (undocumented) export class CollaborationWindow { // (undocumented) @@ -208,9 +205,6 @@ export class CollaborationWindow { minSeq: number; } -// @public @deprecated (undocumented) -export function combine(combiningInfo: ICombiningOp, currentValue: any, newValue: any, seq?: number): any; - // @public (undocumented) export const compareNumbers: (a: number, b: number) => number; @@ -244,7 +238,7 @@ export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg; // @public (undocumented) export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTreeInsertMsg; -// @public @deprecated (undocumented) +// @internal (undocumented) export function createMap(): MapLike; // @public @@ -271,12 +265,6 @@ export interface Dictionary { // @alpha (undocumented) export function discardMergeTreeDeltaRevertible(revertibles: MergeTreeDeltaRevertible[]): void; -// @public @deprecated (undocumented) -export function extend(base: MapLike, extension: MapLike | undefined, combiningOp?: ICombiningOp, seq?: number): MapLike; - -// @public @deprecated (undocumented) -export function extendIfUndefined(base: MapLike, extension: MapLike | undefined): MapLike; - // @internal export function getSlideToSegoff(segoff: { segment: ISegment | undefined; @@ -719,7 +707,7 @@ export class Marker extends BaseSegment implements ReferencePosition { readonly type = "Marker"; } -// @public @deprecated (undocumented) +// @internal (undocumented) export function matchProperties(a: PropertySet | undefined, b: PropertySet | undefined): boolean; // @public (undocumented) @@ -1090,20 +1078,6 @@ export abstract class SortedSet { get size(): number; } -// @public @deprecated (undocumented) -export class Stack { - // (undocumented) - empty(): boolean; - // (undocumented) - items: T[]; - // (undocumented) - pop(): T | undefined; - // (undocumented) - push(val: T): void; - // (undocumented) - top(): T | undefined; -} - // @public (undocumented) export class TextSegment extends BaseSegment { constructor(text: string); diff --git a/packages/dds/matrix/src/test/undoRedoStackManager.ts b/packages/dds/matrix/src/test/undoRedoStackManager.ts index fd22a837a33f..449c02175c5b 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/merge-tree/package.json b/packages/dds/merge-tree/package.json index 89f8920d6053..72a9bd6814d0 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -132,6 +132,30 @@ "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 } } } diff --git a/packages/dds/merge-tree/src/attributionCollection.ts b/packages/dds/merge-tree/src/attributionCollection.ts index 7393857732f0..e53c424c5754 100644 --- a/packages/dds/merge-tree/src/attributionCollection.ts +++ b/packages/dds/merge-tree/src/attributionCollection.ts @@ -167,6 +167,7 @@ export class AttributionCollection implements IAttributionCollection { - 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/index.ts b/packages/dds/merge-tree/src/index.ts index cf99596e2a43..007613c8b537 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -26,7 +26,6 @@ export { RBNodeActions, RedBlackTree, SortedDictionary, - Stack, } from "./collections"; export { LocalClientId, @@ -111,11 +110,7 @@ export { } from "./ops"; export { addProperties, - clone, - combine, createMap, - extend, - extendIfUndefined, IConsensusValue, MapLike, matchProperties, diff --git a/packages/dds/merge-tree/src/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index d2255a6b6b94..ad69db848675 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -9,7 +9,6 @@ import { List, ListNode, walkList } from "./collections"; import { ISegment } from "./mergeTreeNodes"; import { TrackingGroup, TrackingGroupCollection } from "./mergeTreeTracking"; import { ICombiningOp, ReferenceType } from "./ops"; -// eslint-disable-next-line import/no-deprecated import { addProperties, PropertySet } from "./properties"; import { refHasTileLabels, ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; @@ -127,7 +126,6 @@ class LocalReference implements LocalReferencePosition { } public addProperties(newProps: PropertySet, op?: ICombiningOp) { - // eslint-disable-next-line import/no-deprecated this.properties = addProperties(this.properties, newProps, op); } diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 5bd461c57532..fd82bf4018de 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -4,8 +4,7 @@ */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ - -/* eslint-disable @typescript-eslint/prefer-optional-chain, no-bitwise */ +/* eslint-disable no-bitwise */ import { assert } from "@fluidframework/core-utils"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils"; @@ -64,8 +63,7 @@ import { ReferenceType, } from "./ops"; import { PartialSequenceLengths } from "./partialLengths"; -// eslint-disable-next-line import/no-deprecated -import { createMap, extend, MapLike, PropertySet } from "./properties"; +import { createMap, extend, extendIfUndefined, MapLike, PropertySet } from "./properties"; import { refTypeIncludesFlag, ReferencePosition, @@ -228,8 +226,7 @@ function addNodeReferences( } else { const baseSegment = node as BaseSegment; if ( - baseSegment.localRefs && - baseSegment.localRefs.hierRefCount !== undefined && + baseSegment.localRefs?.hierRefCount !== undefined && baseSegment.localRefs.hierRefCount > 0 ) { for (const lref of baseSegment.localRefs) { @@ -243,32 +240,18 @@ function addNodeReferences( } } else { const block = node as IHierBlock; - // eslint-disable-next-line import/no-deprecated 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; 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(); } @@ -277,14 +260,6 @@ class HierMergeBlock extends MergeBlock implements IHierBlock { } } -/** - * @internal - */ -export interface ClientSeq { - refSeq: number; - clientId: string; -} - export interface IMergeTreeOptions { catchUpBlobName?: string; /** @@ -377,14 +352,6 @@ export interface AttributionPolicy { serializer: IAttributionCollectionSerializer; } -/** - * @internal - */ -export const clientSeqComparer: Comparer = { - min: { refSeq: -1, clientId: "" }, - compare: (a, b) => a.refSeq - b.refSeq, -}; - /** * @internal */ @@ -1582,7 +1549,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 { @@ -2423,9 +2390,7 @@ export class MergeTree { 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(); } for (let i = 0; i < block.childCount; i++) { diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index a1baff588bfc..12c4b9df7259 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -5,8 +5,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/prefer-optional-chain */ - import { assert } from "@fluidframework/core-utils"; import { AttributionKey } from "@fluidframework/runtime-definitions"; import { IAttributionCollection } from "./attributionCollection"; @@ -17,7 +15,6 @@ import { TrackingGroupCollection } from "./mergeTreeTracking"; import { ICombiningOp, 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, ReferencePosition, refGetTileLabels } from "./referencePositions"; import { SegmentGroupCollection } from "./segmentGroupCollection"; @@ -408,14 +405,13 @@ export abstract class BaseSegment extends MergeNode implements ISegment { 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, + collabWindow?.collaborating, rollback, ); } @@ -431,7 +427,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment { 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 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/properties.ts b/packages/dds/merge-tree/src/properties.ts index 425f5bf69734..64641d0868cb 100644 --- a/packages/dds/merge-tree/src/properties.ts +++ b/packages/dds/merge-tree/src/properties.ts @@ -21,10 +21,6 @@ export interface IConsensusValue { value: any; } -/** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. - */ export function combine( combiningInfo: ICombiningOp, currentValue: any, @@ -73,8 +69,7 @@ export function combine( } /** - * @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) { if (!a && !b) { @@ -103,10 +98,6 @@ export function matchProperties(a: PropertySet | undefined, b: PropertySet | und return true; } -/** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. - */ export function extend( base: MapLike, extension: MapLike | undefined, @@ -131,10 +122,6 @@ export function extend( return base; } -/** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. - */ export function clone(extension: MapLike | undefined) { if (extension === undefined) { return undefined; @@ -151,8 +138,7 @@ export function clone(extension: MapLike | undefined) { } /** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. + * @internal */ export function addProperties( oldProps: PropertySet | undefined, @@ -168,10 +154,6 @@ export function addProperties( return _oldProps; } -/** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. - */ export function extendIfUndefined(base: MapLike, extension: MapLike | undefined) { if (extension !== undefined) { // eslint-disable-next-line no-restricted-syntax @@ -185,8 +167,7 @@ export function extendIfUndefined(base: MapLike, extension: MapLike | u } /** - * @deprecated - This functionality was not intended for public export and will - * be removed in a future release. + * @internal */ // Create a MapLike with good performance. export function createMap(): MapLike { diff --git a/packages/dds/merge-tree/src/revertibles.ts b/packages/dds/merge-tree/src/revertibles.ts index 15970758104e..2df11e01648f 100644 --- a/packages/dds/merge-tree/src/revertibles.ts +++ b/packages/dds/merge-tree/src/revertibles.ts @@ -13,7 +13,6 @@ import { IMergeLeaf, ISegment, toRemovalInfo } from "./mergeTreeNodes"; import { depthFirstNodeWalk } from "./mergeTreeNodeWalk"; import { ITrackingGroup, Trackable, UnorderedTrackingGroup } from "./mergeTreeTracking"; import { IJSONSegment, MergeTreeDeltaType, ReferenceType } from "./ops"; -// eslint-disable-next-line import/no-deprecated import { matchProperties, PropertySet } from "./properties"; import { DetachedReferencePosition } from "./referencePositions"; import { MergeTree, findRootMergeBlock } from "./mergeTree"; @@ -173,7 +172,6 @@ function appendLocalAnnotateToRevertibles( if (propertyDeltas) { if ( last?.operation === MergeTreeDeltaType.ANNOTATE && - // eslint-disable-next-line import/no-deprecated matchProperties(last?.propertyDeltas, propertyDeltas) ) { last.trackingGroup.link(ds.segment); diff --git a/packages/dds/merge-tree/src/segmentPropertiesManager.ts b/packages/dds/merge-tree/src/segmentPropertiesManager.ts index a7be355ccce9..495fcf3e5c1d 100644 --- a/packages/dds/merge-tree/src/segmentPropertiesManager.ts +++ b/packages/dds/merge-tree/src/segmentPropertiesManager.ts @@ -8,7 +8,6 @@ import { assert } from "@fluidframework/core-utils"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; import { ICombiningOp, IMergeTreeAnnotateMsg } from "./ops"; -// eslint-disable-next-line import/no-deprecated import { combine, createMap, MapLike, PropertySet } from "./properties"; export enum PropertiesRollback { @@ -66,7 +65,6 @@ export class PropertiesManager { collaborating: boolean = false, rollback: PropertiesRollback = PropertiesRollback.None, ): PropertySet | undefined { - // eslint-disable-next-line import/no-deprecated this.pendingKeyUpdateCount ??= createMap(); // There are outstanding local rewrites, so block all non-local changes @@ -143,8 +141,7 @@ export class PropertiesManager { // The delta should be null if undefined, as that's how we encode delete deltas[key] = previousValue === undefined ? null : previousValue; const newValue = combiningOp - ? // eslint-disable-next-line import/no-deprecated - combine(combiningOp, previousValue, undefined, seq) + ? combine(combiningOp, previousValue, undefined, seq) : newProps[key]; if (newValue === null) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete @@ -163,7 +160,7 @@ export class PropertiesManager { newManager: PropertiesManager, ): PropertySet | undefined { if (oldProps) { - // eslint-disable-next-line no-param-reassign, import/no-deprecated + // eslint-disable-next-line no-param-reassign newProps ??= createMap(); if (!newManager) { throw new Error("Must provide new PropertyManager"); @@ -172,7 +169,6 @@ export class PropertiesManager { 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]; diff --git a/packages/dds/merge-tree/src/snapshotV1.ts b/packages/dds/merge-tree/src/snapshotV1.ts index 8ba3e0cf7a8a..fab23c9263e8 100644 --- a/packages/dds/merge-tree/src/snapshotV1.ts +++ b/packages/dds/merge-tree/src/snapshotV1.ts @@ -13,7 +13,6 @@ import { AttributionKey, ISummaryTreeWithStats } from "@fluidframework/runtime-d import { SummaryTreeBuilder } from "@fluidframework/runtime-utils"; import { UnassignedSequenceNumber } from "./constants"; import { ISegment } from "./mergeTreeNodes"; -// eslint-disable-next-line import/no-deprecated import { matchProperties, PropertySet } from "./properties"; import { IJSONSegmentWithMergeInfo, @@ -238,7 +237,6 @@ export class SnapshotV1 { prev = segment; } else if ( prev.canAppend(segment) && - // eslint-disable-next-line import/no-deprecated matchProperties(prev.properties, segment.properties) ) { // We have a compatible pair. Replace `prev` with the coalesced segment. Clone to avoid diff --git a/packages/dds/merge-tree/src/snapshotlegacy.ts b/packages/dds/merge-tree/src/snapshotlegacy.ts index fcc952a03394..cadb39a4cb15 100644 --- a/packages/dds/merge-tree/src/snapshotlegacy.ts +++ b/packages/dds/merge-tree/src/snapshotlegacy.ts @@ -14,7 +14,6 @@ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions" import { SummaryTreeBuilder } from "@fluidframework/runtime-utils"; import { NonCollabClient, UnassignedSequenceNumber } from "./constants"; import { ISegment } from "./mergeTreeNodes"; -// eslint-disable-next-line import/no-deprecated import { matchProperties } from "./properties"; import { JsonSegmentSpecs, @@ -217,7 +216,6 @@ export class SnapshotLegacy { ) { if ( prev?.canAppend(segment) && - // eslint-disable-next-line import/no-deprecated matchProperties(prev.properties, segment.properties) ) { prev = prev.clone(); 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 { doOverRange( { min: 1, max: 16 }, diff --git a/packages/dds/merge-tree/src/test/testServer.ts b/packages/dds/merge-tree/src/test/testServer.ts index 3c451a420b92..8c7fe0d9562f 100644 --- a/packages/dds/merge-tree/src/test/testServer.ts +++ b/packages/dds/merge-tree/src/test/testServer.ts @@ -4,13 +4,22 @@ */ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { Heap, RedBlackTree } from "../collections"; +import { Comparer, Heap, RedBlackTree } from "../collections"; import { compareNumbers } from "../mergeTreeNodes"; -import { ClientSeq, clientSeqComparer } from "../mergeTree"; import { PropertySet } from "../properties"; import { MergeTreeTextHelper } from "../MergeTreeTextHelper"; import { TestClient } from "./testClient"; +interface ClientSeq { + refSeq: number; + clientId: string; +} + +const clientSeqComparer: Comparer = { + min: { refSeq: -1, clientId: "" }, + compare: (a, b) => a.refSeq - b.refSeq, +}; + /** * Server for tests. Simulates client communication by directing placing * messages in client queues. @@ -18,22 +27,12 @@ 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); 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.clients = clients; @@ -44,9 +43,7 @@ export class TestServer extends TestClient { }); } } - addListeners(listeners: TestClient[]) { - this.messageListeners = listeners; - } + applyMsg(msg: ISequencedDocumentMessage) { super.applyMsg(msg); if (TestClient.useCheckQ) { @@ -56,6 +53,7 @@ export class TestServer extends TestClient { return false; } } + // TODO: remove mappings when no longer needed using min seq // in upstream message transformUpstreamMessage(msg: ISequencedDocumentMessage) { @@ -71,6 +69,7 @@ export class TestServer extends TestClient { this.upstreamMap.put(msg.sequenceNumber, this.seq); msg.sequenceNumber = -1; } + copyMsg(msg: ISequencedDocumentMessage) { return { clientId: msg.clientId, @@ -118,11 +117,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; 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 482fa9f41e7b..0242e58a4fe4 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -2041,26 +2041,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>); -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>); -use_old_ClassDeclaration_Stack( - get_current_ClassDeclaration_Stack()); /* * Validate forward compat by using old type in place of current type @@ -2281,50 +2269,26 @@ 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); -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); -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); -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); -use_old_FunctionDeclaration_combine( - get_current_FunctionDeclaration_combine()); /* * Validate forward compat by using old type in place of current type @@ -2665,50 +2629,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); -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); -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); -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); -use_old_FunctionDeclaration_extendIfUndefined( - get_current_FunctionDeclaration_extendIfUndefined()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 490dec9dbf3a..340a3c1c169a 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -16,7 +16,6 @@ import { seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; -// eslint-disable-next-line import/no-deprecated import { matchProperties } from "./properties"; export const zamboniSegmentsMax = 2; @@ -166,7 +165,6 @@ function scourNode(node: IMergeBlock, holdNodes: IMergeNode[], mergeTree: MergeT const segmentHasPositiveLength = (mergeTree.localNetLength(segment) ?? 0) > 0; const canAppend = prevSegment?.canAppend(segment) && - // eslint-disable-next-line import/no-deprecated matchProperties(prevSegment.properties, segment.properties) && prevSegment.trackingCollection.matches(segment.trackingCollection) && segmentHasPositiveLength; From 1cf238910890c71bede0f9c98fc27ddaaa024bbb Mon Sep 17 00:00:00 2001 From: Tony Murphy Date: Tue, 10 Oct 2023 17:23:44 -0700 Subject: [PATCH 07/50] Remove Pending State Deprecations (#17704) ## Remove Pending State Related Deprecations This change removes the deprecated `IPendingLocalState` and `IRuntiume.notifyAttaching`. There is no replacement as they are not longer used. ## 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 fixes [AB#5796](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/5796) --------- Co-authored-by: Tony Murphy --- .changeset/little-mirrors-carry.md | 8 ++++++++ .changeset/open-women-yawn.md | 10 ++++++++++ api-report/container-definitions.api.md | 14 ++----------- api-report/container-runtime.api.md | 2 -- api-report/runtime-utils.api.md | 3 ++- .../common/container-definitions/package.json | 13 +++++++++++- .../common/container-definitions/src/index.ts | 1 - .../container-definitions/src/loader.ts | 15 +++----------- .../container-definitions/src/runtime.ts | 9 +-------- ...eContainerDefinitionsPrevious.generated.ts | 18 ++++------------- .../src/containerStorageAdapter.ts | 10 ++++++---- .../src/test/snapshotConversionTest.spec.ts | 20 ++++++++----------- .../runtime/container-runtime/package.json | 6 +++++- .../container-runtime/src/containerRuntime.ts | 2 -- ...idateContainerRuntimePrevious.generated.ts | 1 + .../datastore/src/localChannelContext.ts | 20 ++++++++++--------- .../runtime/runtime-utils/src/summaryUtils.ts | 10 ++++++---- .../src/test/container.spec.ts | 6 ++---- .../test/deRehydrateContainerTests.spec.ts | 4 ++-- 19 files changed, 83 insertions(+), 89 deletions(-) create mode 100644 .changeset/little-mirrors-carry.md create mode 100644 .changeset/open-women-yawn.md 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/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/api-report/container-definitions.api.md b/api-report/container-definitions.api.md index b49d624ce3a8..cdaa69e84167 100644 --- a/api-report/container-definitions.api.md +++ b/api-report/container-definitions.api.md @@ -420,14 +420,6 @@ export type ILoaderOptions = { maxClientLeaveWaitTime?: number; }; -// @public @deprecated (undocumented) -export interface IPendingLocalState { - // (undocumented) - pendingRuntimeState: unknown; - // (undocumented) - url: string; -} - // @public (undocumented) export interface IProvideFluidCodeDetailsComparer { // (undocumented) @@ -459,8 +451,6 @@ export interface IRuntime extends IDisposable { getPendingLocalState(props?: { notifyImminentClosure?: boolean; }): unknown; - // @deprecated - notifyAttaching(snapshot: ISnapshotTreeWithBlobContents): void; notifyOpReplay?(message: ISequencedDocumentMessage): Promise; process(message: ISequencedDocumentMessage, local: boolean): any; processSignal(message: any, local: boolean): any; @@ -487,10 +477,10 @@ export const isFluidCodeDetails: (details: unknown) => details is Readonly pkg is Readonly; -// @public +// @internal export interface ISnapshotTreeWithBlobContents extends ISnapshotTree { // (undocumented) - blobsContents: { + blobsContents?: { [path: string]: ArrayBufferLike; }; // (undocumented) diff --git a/api-report/container-runtime.api.md b/api-report/container-runtime.api.md index 9fe485684e07..01772a129da4 100644 --- a/api-report/container-runtime.api.md +++ b/api-report/container-runtime.api.md @@ -189,8 +189,6 @@ export class ContainerRuntime extends TypedEventEmitter; // (undocumented) readonly options: ILoaderOptions; diff --git a/api-report/runtime-utils.api.md b/api-report/runtime-utils.api.md index e00e32565f43..dc5f1dc2e798 100644 --- a/api-report/runtime-utils.api.md +++ b/api-report/runtime-utils.api.md @@ -20,6 +20,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 +45,7 @@ export function addTreeToSummary(summary: ISummaryTreeWithStats, key: string, su export function calculateStats(summary: SummaryObject): ISummaryStats; // @public -export function convertSnapshotTreeToSummaryTree(snapshot: ISnapshotTree): ISummaryTreeWithStats; +export function convertSnapshotTreeToSummaryTree(snapshot: ISnapshotTreeWithBlobContents): ISummaryTreeWithStats; // @public export function convertSummaryTreeToITree(summaryTree: ISummaryTree): ITree; diff --git a/packages/common/container-definitions/package.json b/packages/common/container-definitions/package.json index adfebdefab79..7f2405bab1d7 100644 --- a/packages/common/container-definitions/package.json +++ b/packages/common/container-definitions/package.json @@ -60,6 +60,17 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "RemovedInterfaceDeclaration_IPendingLocalState": { + "forwardCompat": false, + "backCompat": false + }, + "InterfaceDeclaration_IRuntime": { + "backCompat": false + }, + "InterfaceDeclaration_ISnapshotTreeWithBlobContents": { + "backCompat": false + } + } } } diff --git a/packages/common/container-definitions/src/index.ts b/packages/common/container-definitions/src/index.ts index 3d96d9c23d6b..1d56cb6f7a30 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 a3ee864fc383..62d04491670a 100644 --- a/packages/common/container-definitions/src/loader.ts +++ b/packages/common/container-definitions/src/loader.ts @@ -677,23 +677,14 @@ 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. - */ -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}. * - * @remarks This is used as the `ContainerContext`'s base snapshot when attaching. + * + * @internal */ 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 e31af70a1ebf..2cac0feb5500 100644 --- a/packages/common/container-definitions/src/runtime.ts +++ b/packages/common/container-definitions/src/runtime.ts @@ -26,7 +26,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"; /** @@ -102,13 +102,6 @@ export interface IRuntime extends IDisposable { */ getPendingLocalState(props?: { notifyImminentClosure?: boolean }): 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. 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 0bf21a78b007..6f33041e5215 100644 --- a/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts +++ b/packages/common/container-definitions/src/test/types/validateContainerDefinitionsPrevious.generated.ts @@ -1032,26 +1032,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); -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); -use_old_InterfaceDeclaration_IPendingLocalState( - get_current_InterfaceDeclaration_IPendingLocalState()); /* * Validate forward compat by using old type in place of current type @@ -1171,6 +1159,7 @@ declare function get_current_InterfaceDeclaration_IRuntime(): declare function use_old_InterfaceDeclaration_IRuntime( use: TypeOnly); use_old_InterfaceDeclaration_IRuntime( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IRuntime()); /* @@ -1243,6 +1232,7 @@ declare function get_current_InterfaceDeclaration_ISnapshotTreeWithBlobContents( declare function use_old_InterfaceDeclaration_ISnapshotTreeWithBlobContents( use: TypeOnly); use_old_InterfaceDeclaration_ISnapshotTreeWithBlobContents( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_ISnapshotTreeWithBlobContents()); /* diff --git a/packages/loader/container-loader/src/containerStorageAdapter.ts b/packages/loader/container-loader/src/containerStorageAdapter.ts index 3741fdb8b2ad..785c39a04a73 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); @@ -302,7 +304,7 @@ function getBlobContentsFromTreeWithBlobContentsCore( } } for (const id of Object.values(tree.blobs)) { - const blob = tree.blobsContents[id]; + const blob = tree.blobsContents?.[id]; assert(blob !== undefined, 0x2ec /* "Blob must be present in blobsContents" */); // ArrayBufferLike will not survive JSON.stringify() blobs[id] = bufferToString(blob, "utf8"); @@ -315,7 +317,7 @@ function getBlobManagerTreeFromTreeWithBlobContents( blobs: ISerializableBlobContents, ) { const id = tree.blobs[redirectTableBlobName]; - const blob = tree.blobsContents[id]; + const blob = tree.blobsContents?.[id]; assert(blob !== undefined, 0x70f /* Blob must be present in blobsContents */); // ArrayBufferLike will not survive JSON.stringify() blobs[id] = bufferToString(blob, "utf8"); 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/runtime/container-runtime/package.json b/packages/runtime/container-runtime/package.json index f37a6118de16..bc8114f4e64f 100644 --- a/packages/runtime/container-runtime/package.json +++ b/packages/runtime/container-runtime/package.json @@ -110,6 +110,10 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_ContainerRuntime": { + "backCompat": false + } + } } } diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 7acf5db2bd8d..6322f1d6d76d 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -3886,8 +3886,6 @@ export class ContainerRuntime ); } - public notifyAttaching() {} // do nothing (deprecated method) - public async getPendingLocalState(props?: { notifyImminentClosure: boolean; }): Promise { 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 fd3cf37bd3c3..082aee02ae64 100644 --- a/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts +++ b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts @@ -163,6 +163,7 @@ declare function get_current_ClassDeclaration_ContainerRuntime(): declare function use_old_ClassDeclaration_ContainerRuntime( use: TypeOnly); use_old_ClassDeclaration_ContainerRuntime( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_ContainerRuntime()); /* diff --git a/packages/runtime/datastore/src/localChannelContext.ts b/packages/runtime/datastore/src/localChannelContext.ts index 4221397f403c..0e78f61c370a 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/runtime-utils/src/summaryUtils.ts b/packages/runtime/runtime-utils/src/summaryUtils.ts index ee50fa437ba5..c5046b397d1c 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. @@ -273,12 +273,14 @@ export function convertToSummaryTree(snapshot: ITree, fullTree: boolean = false) * was taken by serialize api in detached container. * @param snapshot - snapshot in ISnapshotTree format */ -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/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 cef25d7acb0d..9c1715fad89e 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-internal/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, @@ -348,9 +346,9 @@ describeNoCompat("Container", (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 () => { 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 47512f040ec2..bd278128c85f 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 @@ -155,7 +155,7 @@ describeFullCompat(`Dehydrate Rehydrate Container Test`, (getTestObjectProvider) 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; } @@ -278,7 +278,7 @@ describeFullCompat(`Dehydrate Rehydrate Container Test`, (getTestObjectProvider) // 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", ); From c368b11215bcbc469d1df31dae6319f4ec4974b6 Mon Sep 17 00:00:00 2001 From: Scott Norton Date: Wed, 11 Oct 2023 16:30:05 -0400 Subject: [PATCH 08/50] fix (server): Retroactively add changesets for server changes (#17722) ## Description This PR retroactively adds changesets for commits d840deda657ff33a7767933bb796a89ffdf44d90 and a8692f9a72eb6613b675f20487a1f1055e059ed7. [AB#5457](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/5457) --- server/routerlicious/.changeset/blue-crews-cry.md | 9 +++++++++ server/routerlicious/.changeset/ready-colts-bake.md | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 server/routerlicious/.changeset/blue-crews-cry.md create mode 100644 server/routerlicious/.changeset/ready-colts-bake.md diff --git a/server/routerlicious/.changeset/blue-crews-cry.md b/server/routerlicious/.changeset/blue-crews-cry.md new file mode 100644 index 000000000000..abd4111d325d --- /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 changeset was retroactively added for commit `d840deda657ff33a7767933bb796a89ffdf44d90`. diff --git a/server/routerlicious/.changeset/ready-colts-bake.md b/server/routerlicious/.changeset/ready-colts-bake.md new file mode 100644 index 000000000000..f475937b7186 --- /dev/null +++ b/server/routerlicious/.changeset/ready-colts-bake.md @@ -0,0 +1,9 @@ +--- +"@fluidframework/server-services-client": major +--- + +Export ITimeoutContext, getGlobalTimeoutContext, and setGlobalTimeoutContext from @fluidframework/server-services-client + +The `@fluidframework/server-services-client` package now exports interface `ITimeoutContext` and functions `getGlobalTimeoutContext` and `setGlobalTimeoutContext`. + +This changeset was retroactively added for commit `a8692f9a72eb6613b675f20487a1f1055e059ed7`. From 19be7a0c4b00401ac80f7c9d8a8d0e58bc4de777 Mon Sep 17 00:00:00 2001 From: Scott Norton Date: Wed, 11 Oct 2023 23:48:08 -0400 Subject: [PATCH 09/50] (fix) server: Update changesets from #17722 (#17730) ## Description Update changesets from https://github.com/microsoft/FluidFramework/pull/17722 --------- Co-authored-by: Tyler Butler --- server/routerlicious/.changeset/blue-crews-cry.md | 2 +- server/routerlicious/.changeset/ready-colts-bake.md | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/server/routerlicious/.changeset/blue-crews-cry.md b/server/routerlicious/.changeset/blue-crews-cry.md index abd4111d325d..1cf91fd8f72d 100644 --- a/server/routerlicious/.changeset/blue-crews-cry.md +++ b/server/routerlicious/.changeset/blue-crews-cry.md @@ -6,4 +6,4 @@ Use RawAxiosRequestHeaders instead of AxiosRequestHeaders in BasicRestWrapper co The `BasicRestWrapper` class constructor now uses `RawAxiosRequestHeaders` from the `axios` package instead of `AxiosRequestHeaders`. This applies to both the `defaultHeaders` and `refreshDefaultHeaders` arguments. -This changeset was retroactively added for commit `d840deda657ff33a7767933bb796a89ffdf44d90`. +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 index f475937b7186..5f9f46c8bae5 100644 --- a/server/routerlicious/.changeset/ready-colts-bake.md +++ b/server/routerlicious/.changeset/ready-colts-bake.md @@ -4,6 +4,10 @@ Export ITimeoutContext, getGlobalTimeoutContext, and setGlobalTimeoutContext from @fluidframework/server-services-client -The `@fluidframework/server-services-client` package now exports interface `ITimeoutContext` and functions `getGlobalTimeoutContext` and `setGlobalTimeoutContext`. +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. -This changeset was retroactively added for commit `a8692f9a72eb6613b675f20487a1f1055e059ed7`. +- `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). From cecf3343b3daf57d036c1c2e116590c8bda5112b Mon Sep 17 00:00:00 2001 From: Scott Norton Date: Thu, 12 Oct 2023 13:03:45 -0400 Subject: [PATCH 10/50] fix (server): Add @internal tag to new APIs (#17738) ## Description Tagging newly added internal APIs with the `@internal` decorator. Follow up from https://github.com/microsoft/FluidFramework/pull/17730 --- .../api-report/server-services-client.api.md | 6 +++--- .../services-client/src/timeoutContext.ts | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/server/routerlicious/api-report/server-services-client.api.md b/server/routerlicious/api-report/server-services-client.api.md index b3e8bd6a28c3..2a80d7f6a9c0 100644 --- a/server/routerlicious/api-report/server-services-client.api.md +++ b/server/routerlicious/api-report/server-services-client.api.md @@ -94,7 +94,7 @@ export function generateUser(): IUser; // @public (undocumented) export const getAuthorizationTokenFromCredentials: (credentials: ICredentials) => string; -// @public (undocumented) +// @internal export const getGlobalTimeoutContext: () => ITimeoutContext; // @public (undocumented) @@ -397,7 +397,7 @@ export interface ISummaryUploadManager { writeSummaryTree(summaryTree: api.ISummaryTree, parentHandle: string, summaryType: IWholeSummaryPayloadType, sequenceNumber?: number): Promise; } -// @public (undocumented) +// @internal export interface ITimeoutContext { bindTimeout(maxDurationMs: number, callback: () => void): void; bindTimeoutAsync(maxDurationMs: number, callback: () => Promise): Promise; @@ -588,7 +588,7 @@ export abstract class RestWrapper { protected abstract request(options: AxiosRequestConfig, statusCode: number): Promise; } -// @public (undocumented) +// @internal export const setGlobalTimeoutContext: (timeoutContext: ITimeoutContext) => void; // @public diff --git a/server/routerlicious/packages/services-client/src/timeoutContext.ts b/server/routerlicious/packages/services-client/src/timeoutContext.ts index a13404ca4840..912abde5b364 100644 --- a/server/routerlicious/packages/services-client/src/timeoutContext.ts +++ b/server/routerlicious/packages/services-client/src/timeoutContext.ts @@ -3,6 +3,12 @@ * Licensed under the MIT License. */ +/** + * 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 { /** * Attaches timeout info to the callback context. @@ -39,9 +45,20 @@ const nullTimeoutContext = new NullTimeoutContext(); 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) => { if (!getGlobal()) return; getGlobal().timeoutContext = timeoutContext; From 7457b2334960824398fdf58ba45adeccbd41b57e Mon Sep 17 00:00:00 2001 From: msfluid-bot <69654365+msfluid-bot@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:00:14 -0700 Subject: [PATCH 11/50] Automation: main-next integrate (#17748) ## main-next integrate PR ***DO NOT MERGE THIS PR USING THE GITHUB UI.*** The aim of this pull request is to sync main and next branch. This branch has **MERGE CONFLICTS** with next due to this commit. If this PR is assigned to you, you need to do the following: 1. Acknowledge the pull request by adding a comment -- "Actively working on it". 2. Merge main-next-954adff into the target branch, next. **The direction of the merge matters!** You need to checkout next and merge main-next-954adff into it, then fast-forward main-next-954adff to the merge commit. To do that use the following git commands: - `git fetch --all` -- this ensures your remote refs are updated - `git remote -v` -- displays the list of remote repositories associated with your Git repository along with their corresponding URLs. You have to choose the remote associated with the **microsoft/FluidFramework** repository. Change the remote name in these example commands if yours is not upstream. - `git checkout -b next-merge-head upstream/next` -- make a temporary branch at next. - `git merge upstream/main-next-954adff` -- merge next into main-next-954adff 3. Resolve any merge conflicts between the branches, then commit all the changes using the following commands: - `git add .` -- stage all the local changes - `git commit -m "Automation: main-next integrate"` -- commit the merge 4. Fast-forward the main-next-954adff branch to the merge commit and push to the remote. - `git checkout main-next-954adff` -- check out the mergeBranch locally - `git merge next-merge-head --ff-only` -- fast-forward to the merge commit - `git push upstream` -- and push it to upstream (your upstream remote name may be different) 5. Address any CI failures. If you need to make additional changes to the PR, **always amend the commit** using the following git commands: - `git commit --amend -m "Automation: main-next integrate"` - `git push --force-with-lease` Once CI passes and the PR is ready to merge, add the "msftbot: merge-next" label to the PR and one of the people with merge permissions will merge it in. Co-authored-by: Matt Rakow Co-authored-by: Matt Rakow --- examples/apps/tree-comparison/.eslintrc.js | 9 + examples/apps/tree-comparison/.gitignore | 3 + examples/apps/tree-comparison/.npmignore | 6 + examples/apps/tree-comparison/LICENSE | 21 + examples/apps/tree-comparison/README.md | 39 ++ .../tree-comparison/jest-puppeteer.config.js | 19 + examples/apps/tree-comparison/jest.config.js | 33 ++ examples/apps/tree-comparison/package.json | 112 +++++ .../apps/tree-comparison/prettier.config.cjs | 8 + examples/apps/tree-comparison/src/index.html | 15 + .../tree-comparison/src/model/appModel.ts | 17 + .../src/model/containerCode.ts | 58 +++ .../apps/tree-comparison/src/model/index.ts | 6 + .../src/model/inventoryItem.ts | 49 ++ .../src/model/legacyTreeInventoryList.ts | 244 ++++++++++ .../apps/tree-comparison/src/model/schema.ts | 33 ++ .../src/model/treeInventoryList.ts | 188 ++++++++ .../tree-comparison/src/modelInterfaces.ts | 48 ++ examples/apps/tree-comparison/src/start.ts | 59 +++ .../apps/tree-comparison/src/view/appView.tsx | 32 ++ .../tree-comparison/src/view/debugView.tsx | 20 + .../apps/tree-comparison/src/view/index.ts | 7 + .../src/view/inventoryView.tsx | 179 +++++++ .../apps/tree-comparison/tests/index.html | 35 ++ examples/apps/tree-comparison/tests/index.tsx | 99 ++++ .../tests/inventoryList.test.ts | 27 ++ examples/apps/tree-comparison/tsconfig.json | 10 + .../apps/tree-comparison/webpack.config.js | 52 ++ examples/apps/tree-comparison/webpack.dev.js | 9 + examples/apps/tree-comparison/webpack.prod.js | 9 + examples/apps/tree-comparison/webpack.test.js | 55 +++ .../dds/migration-shim/src/packageVersion.ts | 2 +- pnpm-lock.yaml | 456 +++++++++++++++--- 33 files changed, 1883 insertions(+), 76 deletions(-) create mode 100644 examples/apps/tree-comparison/.eslintrc.js create mode 100644 examples/apps/tree-comparison/.gitignore create mode 100644 examples/apps/tree-comparison/.npmignore create mode 100644 examples/apps/tree-comparison/LICENSE create mode 100644 examples/apps/tree-comparison/README.md create mode 100644 examples/apps/tree-comparison/jest-puppeteer.config.js create mode 100644 examples/apps/tree-comparison/jest.config.js create mode 100644 examples/apps/tree-comparison/package.json create mode 100644 examples/apps/tree-comparison/prettier.config.cjs create mode 100644 examples/apps/tree-comparison/src/index.html create mode 100644 examples/apps/tree-comparison/src/model/appModel.ts create mode 100644 examples/apps/tree-comparison/src/model/containerCode.ts create mode 100644 examples/apps/tree-comparison/src/model/index.ts create mode 100644 examples/apps/tree-comparison/src/model/inventoryItem.ts create mode 100644 examples/apps/tree-comparison/src/model/legacyTreeInventoryList.ts create mode 100644 examples/apps/tree-comparison/src/model/schema.ts create mode 100644 examples/apps/tree-comparison/src/model/treeInventoryList.ts create mode 100644 examples/apps/tree-comparison/src/modelInterfaces.ts create mode 100644 examples/apps/tree-comparison/src/start.ts create mode 100644 examples/apps/tree-comparison/src/view/appView.tsx create mode 100644 examples/apps/tree-comparison/src/view/debugView.tsx create mode 100644 examples/apps/tree-comparison/src/view/index.ts create mode 100644 examples/apps/tree-comparison/src/view/inventoryView.tsx create mode 100644 examples/apps/tree-comparison/tests/index.html create mode 100644 examples/apps/tree-comparison/tests/index.tsx create mode 100644 examples/apps/tree-comparison/tests/inventoryList.test.ts create mode 100644 examples/apps/tree-comparison/tsconfig.json create mode 100644 examples/apps/tree-comparison/webpack.config.js create mode 100644 examples/apps/tree-comparison/webpack.dev.js create mode 100644 examples/apps/tree-comparison/webpack.prod.js create mode 100644 examples/apps/tree-comparison/webpack.test.js diff --git a/examples/apps/tree-comparison/.eslintrc.js b/examples/apps/tree-comparison/.eslintrc.js new file mode 100644 index 000000000000..7a2fb77abdf3 --- /dev/null +++ b/examples/apps/tree-comparison/.eslintrc.js @@ -0,0 +1,9 @@ +/*! + * 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: {}, +}; diff --git a/examples/apps/tree-comparison/.gitignore b/examples/apps/tree-comparison/.gitignore new file mode 100644 index 000000000000..abe1eb97c9b2 --- /dev/null +++ b/examples/apps/tree-comparison/.gitignore @@ -0,0 +1,3 @@ +dist +node_modules +lib diff --git a/examples/apps/tree-comparison/.npmignore b/examples/apps/tree-comparison/.npmignore new file mode 100644 index 000000000000..a40f882cf599 --- /dev/null +++ b/examples/apps/tree-comparison/.npmignore @@ -0,0 +1,6 @@ +nyc +*.log +**/*.tsbuildinfo +src/test +dist/test +**/_api-extractor-temp/** diff --git a/examples/apps/tree-comparison/LICENSE b/examples/apps/tree-comparison/LICENSE new file mode 100644 index 000000000000..60af0a6a40e9 --- /dev/null +++ b/examples/apps/tree-comparison/LICENSE @@ -0,0 +1,21 @@ +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/apps/tree-comparison/README.md b/examples/apps/tree-comparison/README.md new file mode 100644 index 000000000000..283617dafd14 --- /dev/null +++ b/examples/apps/tree-comparison/README.md @@ -0,0 +1,39 @@ +# @fluid-example/tree-comparison + +This example compares usage of the legacy SharedTree with the new SharedTree to build the same inventory list application. + + + + + + +## 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/tree-comparison` +1. In a separate terminal, start a Tinylicious server by following the instructions in [Tinylicious](https://github.com/microsoft/FluidFramework/tree/main/server/tinylicious). +1. Run `pnpm start` from this directory and open in a web browser to see the app running. + + + + + +## Testing + +```bash + npm run test:jest +``` + +For in browser testing update `./jest-puppeteer.config.js` to: + +```javascript + launch: { + dumpio: true, // output browser console to cmd line + slowMo: 500, + headless: false, + }, +``` diff --git a/examples/apps/tree-comparison/jest-puppeteer.config.js b/examples/apps/tree-comparison/jest-puppeteer.config.js new file mode 100644 index 000000000000..4b32f8176d62 --- /dev/null +++ b/examples/apps/tree-comparison/jest-puppeteer.config.js @@ -0,0 +1,19 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +module.exports = { + server: { + command: `npm run start:test -- --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/apps/tree-comparison/jest.config.js b/examples/apps/tree-comparison/jest.config.js new file mode 100644 index 000000000000..9719717d00e7 --- /dev/null +++ b/examples/apps/tree-comparison/jest.config.js @@ -0,0 +1,33 @@ +/*! + * 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/apps/tree-comparison/package.json b/examples/apps/tree-comparison/package.json new file mode 100644 index 000000000000..8d0531eddbf6 --- /dev/null +++ b/examples/apps/tree-comparison/package.json @@ -0,0 +1,112 @@ +{ + "name": "@fluid-example/tree-comparison", + "version": "2.0.0-internal.8.0.0", + "private": true, + "description": "Comparing API usage in legacy SharedTree and new SharedTree.", + "homepage": "https://fluidframework.com", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/FluidFramework.git", + "directory": "examples/apps/tree-comparison" + }, + "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 . --ignore-path ../../../.prettierignore", + "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore", + "start": "webpack serve", + "start:test": "webpack serve --config webpack.test.js", + "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-experimental/react-inputs": "workspace:~", + "@fluid-experimental/tree": "workspace:~", + "@fluid-experimental/tree2": "workspace:~", + "@fluid-internal/client-utils": "workspace:~", + "@fluidframework/aqueduct": "workspace:~", + "@fluidframework/container-definitions": "workspace:~", + "@fluidframework/container-loader": "workspace:~", + "@fluidframework/container-runtime-definitions": "workspace:~", + "@fluidframework/core-interfaces": "workspace:~", + "@fluidframework/driver-definitions": "workspace:~", + "@fluidframework/driver-utils": "workspace:~", + "@fluidframework/request-handler": "workspace:~", + "@fluidframework/routerlicious-driver": "workspace:~", + "@fluidframework/runtime-utils": "workspace:~", + "@fluidframework/sequence": "workspace:~", + "@fluidframework/task-manager": "workspace:~", + "@fluidframework/telemetry-utils": "workspace:~", + "@fluidframework/tinylicious-driver": "workspace:~", + "events": "^3.1.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "tiny-typed-emitter": "^2.1.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@fluid-tools/build-cli": "^0.25.0", + "@fluidframework/build-common": "^2.0.1", + "@fluidframework/build-tools": "^0.25.0", + "@fluidframework/eslint-config-fluid": "^3.0.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/node": "^16.18.38", + "@types/puppeteer": "1.3.0", + "@types/react": "^17.0.44", + "@types/react-dom": "^17.0.18", + "@types/uuid": "^9.0.2", + "cross-env": "^7.0.3", + "css-loader": "^1.0.0", + "eslint": "~8.50.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^29.6.2", + "jest-junit": "^10.0.0", + "jest-puppeteer": "^6.2.0", + "prettier": "~3.0.3", + "process": "^0.11.10", + "puppeteer": "^17.1.3", + "rimraf": "^4.4.0", + "style-loader": "^1.0.0", + "ts-jest": "^29.1.1", + "ts-loader": "^9.3.0", + "typescript": "~5.1.6", + "webpack": "^5.82.0", + "webpack-cli": "^4.9.2", + "webpack-dev-server": "~4.6.0", + "webpack-merge": "^5.8.0" + }, + "fluid": { + "browser": { + "umd": { + "files": [ + "main.bundle.js" + ], + "library": "main" + } + } + }, + "typeValidation": { + "disabled": true, + "broken": {} + } +} diff --git a/examples/apps/tree-comparison/prettier.config.cjs b/examples/apps/tree-comparison/prettier.config.cjs new file mode 100644 index 000000000000..d4870022599f --- /dev/null +++ b/examples/apps/tree-comparison/prettier.config.cjs @@ -0,0 +1,8 @@ +/*! + * 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/examples/apps/tree-comparison/src/index.html b/examples/apps/tree-comparison/src/index.html new file mode 100644 index 000000000000..aff11d0f48b9 --- /dev/null +++ b/examples/apps/tree-comparison/src/index.html @@ -0,0 +1,15 @@ + + + + + + + + + Test application + + +
+
+ + diff --git a/examples/apps/tree-comparison/src/model/appModel.ts b/examples/apps/tree-comparison/src/model/appModel.ts new file mode 100644 index 000000000000..ddacf492b1dc --- /dev/null +++ b/examples/apps/tree-comparison/src/model/appModel.ts @@ -0,0 +1,17 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import type { IInventoryListAppModel, IInventoryList } from "../modelInterfaces"; + +/** + * The InventoryListAppModel provides two inventory lists, one using legacy SharedTree + * and the other using new SharedTree. They function the same and share the same interface. + */ +export class InventoryListAppModel implements IInventoryListAppModel { + public constructor( + public readonly legacyTreeInventoryList: IInventoryList, + public readonly treeInventoryList: IInventoryList, + ) {} +} diff --git a/examples/apps/tree-comparison/src/model/containerCode.ts b/examples/apps/tree-comparison/src/model/containerCode.ts new file mode 100644 index 000000000000..63fb5aa924b7 --- /dev/null +++ b/examples/apps/tree-comparison/src/model/containerCode.ts @@ -0,0 +1,58 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { ModelContainerRuntimeFactory } from "@fluid-example/example-utils"; +import type { IContainer } from "@fluidframework/container-definitions"; +import type { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; +// eslint-disable-next-line import/no-deprecated +import { requestFluidObject } from "@fluidframework/runtime-utils"; + +import type { IInventoryList, IInventoryListAppModel } from "../modelInterfaces"; +import { InventoryListAppModel } from "./appModel"; +import { LegacyTreeInventoryListFactory } from "./legacyTreeInventoryList"; +import { TreeInventoryListFactory } from "./treeInventoryList"; + +export const legacyTreeInventoryListId = "legacy-tree-inventory-list"; +export const treeInventoryListId = "tree-inventory-list"; + +export class InventoryListContainerRuntimeFactory extends ModelContainerRuntimeFactory { + public constructor() { + super( + new Map([ + LegacyTreeInventoryListFactory.registryEntry, + TreeInventoryListFactory.registryEntry, + ]), // registryEntries + ); + } + + /** + * {@inheritDoc ModelContainerRuntimeFactory.containerInitializingFirstTime} + */ + protected async containerInitializingFirstTime(runtime: IContainerRuntime) { + const legacyTreeInventoryList = await runtime.createDataStore( + LegacyTreeInventoryListFactory.type, + ); + await legacyTreeInventoryList.trySetAlias(legacyTreeInventoryListId); + const treeInventoryList = await runtime.createDataStore(TreeInventoryListFactory.type); + await treeInventoryList.trySetAlias(treeInventoryListId); + } + + /** + * {@inheritDoc ModelContainerRuntimeFactory.createModel} + */ + protected async createModel(runtime: IContainerRuntime, container: IContainer) { + // eslint-disable-next-line import/no-deprecated + const legacyTreeInventoryList = await requestFluidObject( + await runtime.getRootDataStore(legacyTreeInventoryListId), + "", + ); + // eslint-disable-next-line import/no-deprecated + const treeInventoryList = await requestFluidObject( + await runtime.getRootDataStore(treeInventoryListId), + "", + ); + return new InventoryListAppModel(legacyTreeInventoryList, treeInventoryList); + } +} diff --git a/examples/apps/tree-comparison/src/model/index.ts b/examples/apps/tree-comparison/src/model/index.ts new file mode 100644 index 000000000000..a1848fa4cdf2 --- /dev/null +++ b/examples/apps/tree-comparison/src/model/index.ts @@ -0,0 +1,6 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +export { InventoryListContainerRuntimeFactory } from "./containerCode"; diff --git a/examples/apps/tree-comparison/src/model/inventoryItem.ts b/examples/apps/tree-comparison/src/model/inventoryItem.ts new file mode 100644 index 000000000000..d273b82c47bd --- /dev/null +++ b/examples/apps/tree-comparison/src/model/inventoryItem.ts @@ -0,0 +1,49 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { TypedEmitter } from "tiny-typed-emitter"; + +import type { IInventoryItem, IInventoryItemEvents } from "../modelInterfaces"; + +/** + * InventoryItem is the local object with the friendly interface for the view to use. + * It binds to either legacy or new SharedTree by abstracting out how the values are + * changed. + */ +export class InventoryItem extends TypedEmitter implements IInventoryItem { + public get id() { + return this._id; + } + public get name() { + return this._name; + } + public get quantity() { + return this._quantity; + } + public set quantity(newQuantity: number) { + // Setting the quantity does not directly update the value, but rather roundtrips it through + // the backing data by using the provided callback. We trust that later this will result in + // handleQuantityUpdate getting called when the true backing data changes. + this._setQuantity(newQuantity); + } + /** + * handleQuantityUpdate is not available on IInventoryItem intentionally, since it should not be + * available to the view. Instead it is to be called by the backing data when the true value + * of the data changes. + */ + public handleQuantityUpdate(newQuantity: number) { + this._quantity = newQuantity; + this.emit("quantityChanged"); + } + public constructor( + private readonly _id: string, + private readonly _name: string, + private _quantity: number, + private readonly _setQuantity: (quantity: number) => void, + public readonly deleteItem: () => void, + ) { + super(); + } +} diff --git a/examples/apps/tree-comparison/src/model/legacyTreeInventoryList.ts b/examples/apps/tree-comparison/src/model/legacyTreeInventoryList.ts new file mode 100644 index 000000000000..c37486fba143 --- /dev/null +++ b/examples/apps/tree-comparison/src/model/legacyTreeInventoryList.ts @@ -0,0 +1,244 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { + BuildNode, + Change, + EagerCheckout, + SharedTree as LegacySharedTree, + NodeId, + StablePlace, + StableRange, + TraitLabel, + TreeView, + TreeViewNode, +} from "@fluid-experimental/tree"; +import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; + +import type { IInventoryItem, IInventoryList } from "../modelInterfaces"; +import { InventoryItem } from "./inventoryItem"; + +const legacySharedTreeKey = "legacySharedTree"; + +// TODO: Do I want string here or number? Prob don't want anything tree-specific +// since this gets exposed to the view. I've currently removed any reverse lookups so it doesn't +// matter too much. +const nodeIdToInventoryItemId = (nodeId: NodeId) => nodeId.toString(); + +export class LegacyTreeInventoryList extends DataObject implements IInventoryList { + private _tree: LegacySharedTree | undefined; + private readonly _inventoryItems = new Map(); + + private get tree() { + if (this._tree === undefined) { + throw new Error("Not initialized properly"); + } + return this._tree; + } + + public readonly addItem = (name: string, quantity: number) => { + const addedNode: BuildNode = { + definition: "inventoryItem", + traits: { + name: { + definition: "name", + payload: name, + }, + quantity: { + definition: "quantity", + payload: quantity, + }, + }, + }; + const rootNode = this.tree.currentView.getViewNode(this.tree.currentView.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const inventoryNodeId = rootNode.traits.get("inventory" as TraitLabel)![0]; + this.tree.applyEdit( + Change.insertTree( + addedNode, + StablePlace.atEndOf({ + parent: inventoryNodeId, + label: "inventoryItems" as TraitLabel, + }), + ), + ); + }; + + public readonly getItems = (): IInventoryItem[] => { + return [...this._inventoryItems.values()]; + }; + + protected async initializingFirstTime(): Promise { + const legacySharedTree = this.runtime.createChannel( + undefined, + LegacySharedTree.getFactory().type, + ) as LegacySharedTree; + + const inventoryNode: BuildNode = { + definition: "inventory", + traits: { + // REV: If I try to remove these sample items (leaving inventoryItems as an empty array) + // the trait is removed entirely (undefined). It seems that empty traits are discarded? + // This is a problem when later I want to iterate over it (in hasInitialized, when + // building up my initial set of InventoryItem objects), since I'll get a not-iterable error. + inventoryItems: [ + { + definition: "inventoryItem", + traits: { + // REV: I tried adding an explicit "ID" trait here and using that for tracking, + // rather than the NodeId itself. However, when deleting a node the "removed" member + // of the "viewChange" event args only includes the NodeId and the nodes are already + // gone (cannot be retrieved). So the ID is lost at that point. I could maintain a + // separate NodeId -> ID mapping to track, but this feels kind of clunky and defeats + // the point of getting away from direct usage of NodeIds. + name: { + definition: "name", + payload: "nut", + }, + quantity: { + definition: "quantity", + payload: 0, + }, + }, + }, + { + definition: "inventoryItem", + traits: { + name: { + definition: "name", + payload: "bolt", + }, + quantity: { + definition: "quantity", + payload: 0, + }, + }, + }, + ], + }, + }; + legacySharedTree.applyEdit( + Change.insertTree( + inventoryNode, + StablePlace.atStartOf({ + parent: legacySharedTree.currentView.root, + label: "inventory" as TraitLabel, + }), + ), + ); + + this.root.set(legacySharedTreeKey, legacySharedTree.handle); + } + + /** + * hasInitialized is run by each client as they load the DataObject. Here we use it to set up usage of the + * DataObject, by registering an event listener for changes to the inventory list. + */ + protected async hasInitialized() { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._tree = await this.root + .get>(legacySharedTreeKey)! + .get(); + + // REV: Not exactly sure why a checkout is required to get "viewChange" events, seems like it should be able + // to fire off the legacy shared tree itself? + const checkout = new EagerCheckout(this._tree); + // This event handler fires for any change to the tree, so it needs to handle all possibilities (change, add, remove). + checkout.on("viewChange", (before: TreeView, after: TreeView) => { + const { changed, added, removed } = before.delta(after); + for (const quantityNodeId of changed) { + const quantityNode = this.tree.currentView.getViewNode(quantityNodeId); + // REV: This is annoying to iterate over since I can't filter until I've retrieved the node object. + // When adding a node the "inventory" node changes too, but we don't want to handle that here. + if (quantityNode.definition === "quantity") { + const newQuantity = quantityNode.payload as number; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const inventoryItemNodeId = quantityNode.parentage!.parent; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._inventoryItems + .get(nodeIdToInventoryItemId(inventoryItemNodeId))! + .handleQuantityUpdate(newQuantity); + } + } + + for (const inventoryNodeId of added) { + const inventoryItemNode = this.tree.currentView.getViewNode(inventoryNodeId); + // REV: Similar to above, this can't be filtered without grabbing the actual node objects. + // This list will include the "name" and "quantity" nodes too, but we only want to handle the "inventoryItem". + if (inventoryItemNode.definition === "inventoryItem") { + const addedInventoryItem = + this.makeInventoryItemFromInventoryItemNode(inventoryItemNode); + this._inventoryItems.set(addedInventoryItem.id, addedInventoryItem); + this.emit("itemAdded", addedInventoryItem); + } + } + + for (const inventoryNodeId of removed) { + // REV: A twist on the filtering issue, but would be nice to have a way to filter the nodes prior to iterating. + // This list will include the "name" and "quantity" nodes too, but we only want to handle the "inventoryItem". + // However, since the nodes were deleted we can't fetch them and filter on definition as for changed/added. + // Instead we'll just compare the IDs against the inventory items we're tracking (since the name/quantity won't + // be in there). + const inventoryItemId = nodeIdToInventoryItemId(inventoryNodeId); + const deletedInventoryItem = this._inventoryItems.get(inventoryItemId); + if (deletedInventoryItem !== undefined) { + this._inventoryItems.delete(inventoryItemId); + this.emit("itemDeleted", deletedInventoryItem); + } + } + }); + + // Last step of initializing is to populate our map of InventoryItems. + // REV: Seems strange that this.tree.currentView.rootNode is private. + const rootNode = this.tree.currentView.getViewNode(this.tree.currentView.root); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const inventoryItemsNodeIds = this.tree.currentView + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .getViewNode(rootNode.traits.get("inventory" as TraitLabel)![0]) + .traits.get("inventoryItems" as TraitLabel)!; + for (const inventoryItemNodeId of inventoryItemsNodeIds) { + const inventoryItemNode = this.tree.currentView.getViewNode(inventoryItemNodeId); + const newInventoryItem = this.makeInventoryItemFromInventoryItemNode(inventoryItemNode); + this._inventoryItems.set(newInventoryItem.id, newInventoryItem); + } + } + + private makeInventoryItemFromInventoryItemNode(inventoryItemNode: TreeViewNode): InventoryItem { + const id = nodeIdToInventoryItemId(inventoryItemNode.identifier); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const nameNodeId = inventoryItemNode.traits.get("name" as TraitLabel)![0]; + const nameNode = this.tree.currentView.getViewNode(nameNodeId); + const name = nameNode.payload as string; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const quantityNodeId = inventoryItemNode.traits.get("quantity" as TraitLabel)![0]; + const quantityNode = this.tree.currentView.getViewNode(quantityNodeId); + const quantity = quantityNode.payload as number; + + const setQuantity = (newQuantity: number) => { + this.tree.applyEdit(Change.setPayload(quantityNodeId, newQuantity)); + }; + + const deleteItem = () => { + this.tree.applyEdit(Change.delete(StableRange.only(inventoryItemNode.identifier))); + }; + + return new InventoryItem(id, name, quantity, setQuantity, deleteItem); + } +} + +/** + * The DataObjectFactory is used by Fluid Framework to instantiate our DataObject. We provide it with a unique name + * and the constructor it will call. The third argument lists the other data structures it will utilize. In this + * scenario, the fourth argument is not used. + */ +export const LegacyTreeInventoryListFactory = new DataObjectFactory( + "legacy-tree-inventory-list", + LegacyTreeInventoryList, + [LegacySharedTree.getFactory()], + {}, +); diff --git a/examples/apps/tree-comparison/src/model/schema.ts b/examples/apps/tree-comparison/src/model/schema.ts new file mode 100644 index 000000000000..43e455b4b4ca --- /dev/null +++ b/examples/apps/tree-comparison/src/model/schema.ts @@ -0,0 +1,33 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { leaf, SchemaBuilder, TypedField, TypedNode } from "@fluid-experimental/tree2"; + +// By importing the leaf library we don't have to define our own string and number types. +const builder = new SchemaBuilder({ scope: "inventory app", libraries: [leaf.library] }); + +const inventoryItem = builder.struct("Contoso:InventoryItem-1.0.0", { + // REV: I added an ID here because I didn't find a unique identifier on the node. + // I'm not necessarily opposed to this, but I wonder if it's needed/duplicative. + id: leaf.string, + name: leaf.string, + quantity: leaf.number, +}); +export type InventoryItemNode = TypedNode; + +// REV: Building this up as a series of builder invocations makes it hard to read the schema. +// Would be nice if instead we could define some single big Serializable or similar that laid the +// schema out and then pass that in. +const inventory = builder.struct("Contoso:Inventory-1.0.0", { + inventoryItems: builder.sequence(inventoryItem), +}); +export type InventoryNode = TypedNode; + +// REV: The rootField feels extra to me. Is there a way to omit it? Something like +// builder.intoDocumentSchema(inventory) +const inventoryField = SchemaBuilder.required(inventory); +export type InventoryField = TypedField; + +export const schema = builder.toDocumentSchema(inventoryField); diff --git a/examples/apps/tree-comparison/src/model/treeInventoryList.ts b/examples/apps/tree-comparison/src/model/treeInventoryList.ts new file mode 100644 index 000000000000..bc1140a14c5b --- /dev/null +++ b/examples/apps/tree-comparison/src/model/treeInventoryList.ts @@ -0,0 +1,188 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { + AllowedUpdateType, + ForestType, + typeboxValidator, + TypedTreeChannel, + TypedTreeFactory, +} from "@fluid-experimental/tree2"; +import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; +import { IFluidHandle } from "@fluidframework/core-interfaces"; +import { v4 as uuid } from "uuid"; + +import type { IInventoryItem, IInventoryList } from "../modelInterfaces"; +import { InventoryItem } from "./inventoryItem"; +import { InventoryNode, InventoryField, InventoryItemNode, schema } from "./schema"; + +const factory = new TypedTreeFactory({ + // REV: I'm not exactly sure why a validator should be passed here? Like what it's used for, + // so it's hard to know what a "correct" choice would be as a result. + jsonValidator: typeboxValidator, + // REV: I copied this from another example but I have no idea what it means - documentation is + // self-referencing. + forest: ForestType.Reference, + // REV: What's the scenario where I'd want to leverage the subtype? Documentation makes it sound + // like it should be optional at least. + subtype: "InventoryList", +}); + +const sharedTreeKey = "sharedTree"; + +export class TreeInventoryList extends DataObject implements IInventoryList { + private _sharedTree: TypedTreeChannel | undefined; + private get sharedTree(): TypedTreeChannel { + if (this._sharedTree === undefined) { + throw new Error("Not initialized properly"); + } + return this._sharedTree; + } + private _inventory: InventoryField | undefined; + private get inventory(): InventoryNode { + if (this._inventory === undefined) { + throw new Error("Not initialized properly"); + } + return this._inventory.content; + } + private readonly _inventoryItems = new Map(); + + public readonly addItem = (name: string, quantity: number) => { + this.inventory.inventoryItems.insertAtEnd([ + { + // REV: I think this might be a good place to use the SharedTree ID generation? + // If so, could use a pointer to how to do that? + id: uuid(), + name, + quantity, + }, + ]); + }; + + public readonly getItems = (): IInventoryItem[] => { + return [...this._inventoryItems.values()]; + }; + + protected async initializingFirstTime(): Promise { + this._sharedTree = this.runtime.createChannel(undefined, factory.type) as TypedTreeChannel; + this.root.set(sharedTreeKey, this._sharedTree.handle); + } + + // REV: Have to use initializingFromExisting here rather than hasInitialized due to a bug - getting + // the handle on the creating client retrieves the wrong object. + protected async initializingFromExisting(): Promise { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._sharedTree = await this.root + .get>(sharedTreeKey)! + .get(); + } + + protected async hasInitialized(): Promise { + // REV: I don't particularly like the combined schematize/initialize. My preference would be for + // separate schematize/initialize calls. + this._inventory = this.sharedTree.schematize({ + initialTree: { + inventoryItems: [ + { + id: uuid(), + name: "nut", + quantity: 0, + }, + { + id: uuid(), + name: "bolt", + quantity: 0, + }, + ], + }, + allowedSchemaModifications: AllowedUpdateType.None, + schema, + }); + // REV: This event feels overly-broad for what I'm looking for, but I'm having issues with + // more node-specific events ("changing", etc.). I also personally find the deviation from + // standard EventEmitter API surprising/unintuitive/inconvenient. + this.inventory.context.on("afterChange", () => { + // Since "afterChange" doesn't provide event args, we need to scan the tree and compare + // it to our InventoryItems to find what changed. This event handler fires for any + // change to the tree, so it needs to handle all possibilities (change, add, remove). + for (const inventoryItemNode of this.inventory.inventoryItems) { + const upToDateQuantity = inventoryItemNode.quantity; + const inventoryItem = this._inventoryItems.get(inventoryItemNode.id); + // If we're not currently tracking some item in the tree, then it must have been + // added in this change. + if (inventoryItem === undefined) { + const newInventoryItem = + this.makeInventoryItemFromInventoryItemNode(inventoryItemNode); + this._inventoryItems.set(inventoryItemNode.id, newInventoryItem); + this.emit("itemAdded"); + } + // If the quantity of our tracking item is different from the tree, then the + // quantity must have changed in this change. + if (inventoryItem !== undefined && inventoryItem.quantity !== upToDateQuantity) { + inventoryItem.handleQuantityUpdate(upToDateQuantity); + } + } + + // Search for deleted inventory items to update our collection + const currentInventoryIds = [...this.inventory.inventoryItems].map( + (inventoryItemNode) => { + return inventoryItemNode.id; + }, + ); + for (const trackedItemId of this._inventoryItems.keys()) { + // If the tree doesn't contain the id of an item we're tracking, then it must have + // been deleted in this change. + if (!currentInventoryIds.includes(trackedItemId)) { + this._inventoryItems.delete(trackedItemId); + this.emit("itemDeleted"); + } + } + }); + + // Last step of initializing is to populate our map of InventoryItems. + for (const inventoryItemNode of this.inventory.inventoryItems) { + const inventoryItem = this.makeInventoryItemFromInventoryItemNode(inventoryItemNode); + this._inventoryItems.set(inventoryItemNode.id, inventoryItem); + } + } + + private makeInventoryItemFromInventoryItemNode( + inventoryItemNode: InventoryItemNode, + ): InventoryItem { + const setQuantity = (newQuantity: number) => { + // REV: This still seems surprising to me that it's making the remote change, I think + // it would be more apparent as .setContent() rather than using the setter. + inventoryItemNode.boxedQuantity.content = newQuantity; + }; + const deleteItem = () => { + // REV: Is this the best way to do this? Was hoping for maybe just an inventoryItemNode.delete(). + this.inventory.inventoryItems.removeAt(inventoryItemNode.parentField.index); + }; + // REV: Per-node events seem buggy (this fires twice per change, presumably for local change + ack?) + // inventoryItemNode.on("changing", () => { + // console.log(`changing: ${inventoryItemNode.quantity}`); + // inventoryItem.handleQuantityUpdate(inventoryItemNode.quantity); + // }); + return new InventoryItem( + inventoryItemNode.id, + inventoryItemNode.name, + inventoryItemNode.quantity, + setQuantity, + deleteItem, + ); + } +} + +/** + * The DataObjectFactory is used by Fluid Framework to instantiate our DataObject. We provide it with a unique name + * and the constructor it will call. The third argument lists the other data structures it will utilize. In this + * scenario, the fourth argument is not used. + */ +export const TreeInventoryListFactory = new DataObjectFactory( + "tree-inventory-list", + TreeInventoryList, + [factory], + {}, +); diff --git a/examples/apps/tree-comparison/src/modelInterfaces.ts b/examples/apps/tree-comparison/src/modelInterfaces.ts new file mode 100644 index 000000000000..b8de2edfa965 --- /dev/null +++ b/examples/apps/tree-comparison/src/modelInterfaces.ts @@ -0,0 +1,48 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { EventEmitter } from "events"; +import { TypedEmitter } from "tiny-typed-emitter"; + +/** + * For demo purposes this is a super-simple interface, but in a real scenario this should have all relevant surface + * for the application to run. + */ +export interface IInventoryListAppModel { + /** + * An inventory tracker list using the legacy shared tree. + */ + readonly legacyTreeInventoryList: IInventoryList; + /** + * An inventory tracker list using the new shared tree. + */ + readonly treeInventoryList: IInventoryList; +} + +export interface IInventoryItemEvents { + quantityChanged: () => void; +} + +export interface IInventoryItem extends TypedEmitter { + readonly id: string; + readonly name: string; + quantity: number; + readonly deleteItem: () => void; +} + +/** + * IInventoryList describes the public API surface for our inventory list object. + */ +export interface IInventoryList extends EventEmitter { + readonly addItem: (name: string, quantity: number) => void; + + readonly getItems: () => IInventoryItem[]; + + /** + * The listChanged event will fire whenever an item is added/removed, either locally or remotely. + * TODO: Consider using tiny-typed-emitter if not using DataObject + */ + on(event: "itemAdded" | "itemDeleted", listener: (item: IInventoryItem) => void): this; +} diff --git a/examples/apps/tree-comparison/src/start.ts b/examples/apps/tree-comparison/src/start.ts new file mode 100644 index 000000000000..1963d4e382a2 --- /dev/null +++ b/examples/apps/tree-comparison/src/start.ts @@ -0,0 +1,59 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import React from "react"; +import ReactDOM from "react-dom"; + +import { StaticCodeLoader, TinyliciousModelLoader } from "@fluid-example/example-utils"; +import { InventoryListContainerRuntimeFactory } from "./model"; +import type { IInventoryListAppModel } from "./modelInterfaces"; +import { DebugView, InventoryListAppView } from "./view"; + +const updateTabForId = (id: string) => { + // Update the URL with the actual ID + location.hash = id; + + // Put the ID in the tab title + document.title = id; +}; + +const render = (model: IInventoryListAppModel) => { + const appDiv = document.getElementById("app") as HTMLDivElement; + ReactDOM.unmountComponentAtNode(appDiv); + ReactDOM.render(React.createElement(InventoryListAppView, { model }), appDiv); + + // The DebugView is just for demo purposes, in case we want to access internal state or have debug controls. + const debugDiv = document.getElementById("debug") as HTMLDivElement; + ReactDOM.unmountComponentAtNode(debugDiv); + ReactDOM.render( + React.createElement(DebugView, { + model, + }), + debugDiv, + ); +}; + +async function start(): Promise { + const modelLoader = new TinyliciousModelLoader( + new StaticCodeLoader(new InventoryListContainerRuntimeFactory()), + ); + + let id: string; + let model: IInventoryListAppModel; + + if (location.hash.length === 0) { + const createResponse = await modelLoader.createDetached("1.0"); + model = createResponse.model; + id = await createResponse.attach(); + } else { + id = location.hash.substring(1); + model = await modelLoader.loadExisting(id); + } + + render(model); + updateTabForId(id); +} + +start().catch((error) => console.error(error)); diff --git a/examples/apps/tree-comparison/src/view/appView.tsx b/examples/apps/tree-comparison/src/view/appView.tsx new file mode 100644 index 000000000000..4f7e346d5776 --- /dev/null +++ b/examples/apps/tree-comparison/src/view/appView.tsx @@ -0,0 +1,32 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import React from "react"; + +import type { IInventoryListAppModel } from "../modelInterfaces"; +import { InventoryListView } from "./inventoryView"; + +export interface IInventoryListAppViewProps { + model: IInventoryListAppModel; +} + +/** + * The InventoryListAppView is the top-level app view. It is made to pair with an InventoryListAppModel and + * render its contents appropriately. + */ +export const InventoryListAppView: React.FC = ( + props: IInventoryListAppViewProps, +) => { + const { model } = props; + + return ( + <> +

Using legacy SharedTree

+ +

Using new SharedTree

+ + + ); +}; diff --git a/examples/apps/tree-comparison/src/view/debugView.tsx b/examples/apps/tree-comparison/src/view/debugView.tsx new file mode 100644 index 000000000000..5a4489a5c2b7 --- /dev/null +++ b/examples/apps/tree-comparison/src/view/debugView.tsx @@ -0,0 +1,20 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import React from "react"; + +import type { IInventoryListAppModel } from "../modelInterfaces"; + +export interface IDebugViewProps { + model: IInventoryListAppModel; +} + +export const DebugView: React.FC = (props: IDebugViewProps) => { + return ( +
+

Debug info

+
+ ); +}; diff --git a/examples/apps/tree-comparison/src/view/index.ts b/examples/apps/tree-comparison/src/view/index.ts new file mode 100644 index 000000000000..f18048387d64 --- /dev/null +++ b/examples/apps/tree-comparison/src/view/index.ts @@ -0,0 +1,7 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +export { IDebugViewProps, DebugView } from "./debugView"; +export { IInventoryListAppViewProps, InventoryListAppView } from "./appView"; diff --git a/examples/apps/tree-comparison/src/view/inventoryView.tsx b/examples/apps/tree-comparison/src/view/inventoryView.tsx new file mode 100644 index 000000000000..09287937ceb7 --- /dev/null +++ b/examples/apps/tree-comparison/src/view/inventoryView.tsx @@ -0,0 +1,179 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import React, { FC, useEffect, useRef, useState } from "react"; +import { IInventoryList, IInventoryItem } from "../modelInterfaces"; + +export interface IInventoryItemViewProps { + inventoryItem: IInventoryItem; + disabled?: boolean; +} + +export const InventoryItemView: FC = (props: IInventoryItemViewProps) => { + const { inventoryItem, disabled } = props; + const quantityRef = useRef(null); + useEffect(() => { + const updateFromRemoteQuantity = () => { + if (quantityRef.current !== null) { + quantityRef.current.value = inventoryItem.quantity.toString(); + } + }; + inventoryItem.on("quantityChanged", updateFromRemoteQuantity); + updateFromRemoteQuantity(); + return () => { + inventoryItem.off("quantityChanged", updateFromRemoteQuantity); + }; + }, [inventoryItem]); + + const inputHandler = (e) => { + const newValue = parseInt(e.target.value, 10); + inventoryItem.quantity = newValue; + }; + + return ( + + {inventoryItem.name} + + + + + + + + ); +}; + +interface IAddItemViewProps { + readonly addItem: (name: string, quantity: number) => void; + disabled?: boolean; +} + +const AddItemView: FC = (props: IAddItemViewProps) => { + const { addItem, disabled } = props; + const nameRef = useRef(null); + const quantityRef = useRef(null); + + const onAddItemButtonClick = () => { + if (nameRef.current === null || quantityRef.current === null) { + throw new Error("Couldn't get the new item info"); + } + + // Extract the values from the inputs and add the new item + const name = nameRef.current.value; + const quantityString = quantityRef.current.value; + const quantity = quantityString !== "" ? parseInt(quantityString, 10) : 0; + addItem(name, quantity); + + // Clear the input form + nameRef.current.value = ""; + quantityRef.current.value = ""; + }; + + return ( + <> + + + + + + + + + + + + + + + ); +}; + +export interface IInventoryListViewProps { + inventoryList: IInventoryList; + disabled?: boolean; +} + +export const InventoryListView: FC = (props: IInventoryListViewProps) => { + const { inventoryList, disabled } = props; + + const [inventoryItems, setInventoryItems] = useState( + inventoryList.getItems(), + ); + useEffect(() => { + const updateItems = () => { + // TODO: This blows away all the inventory items, making the granular add/delete events + // not so useful. Is there a good way to make a more granular change? + setInventoryItems(inventoryList.getItems()); + }; + inventoryList.on("itemAdded", updateItems); + inventoryList.on("itemDeleted", updateItems); + + return () => { + inventoryList.off("itemAdded", updateItems); + inventoryList.off("itemDeleted", updateItems); + }; + }, [inventoryList]); + + const inventoryItemViews = inventoryItems.map((inventoryItem) => { + return ( + + ); + }); + + return ( + + + + + + + + + {inventoryItemViews.length > 0 ? ( + inventoryItemViews + ) : ( + + + + )} + + +
Inventory itemQuantity
No items in inventory
+ ); +}; diff --git a/examples/apps/tree-comparison/tests/index.html b/examples/apps/tree-comparison/tests/index.html new file mode 100644 index 000000000000..1bf361882e2e --- /dev/null +++ b/examples/apps/tree-comparison/tests/index.html @@ -0,0 +1,35 @@ + + + + + + + + + Test - InventoryList + + +
+
+
+
+ + diff --git a/examples/apps/tree-comparison/tests/index.tsx b/examples/apps/tree-comparison/tests/index.tsx new file mode 100644 index 000000000000..90b06b4f3b61 --- /dev/null +++ b/examples/apps/tree-comparison/tests/index.tsx @@ -0,0 +1,99 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { SessionStorageModelLoader, StaticCodeLoader } from "@fluid-example/example-utils"; + +import React from "react"; +import ReactDOM from "react-dom"; + +import { InventoryListContainerRuntimeFactory } from "../src/model"; +import type { IInventoryListAppModel } from "../src/modelInterfaces"; +import { DebugView, InventoryListAppView } from "../src/view"; + +const updateTabForId = (id: string) => { + // Update the URL with the actual ID + location.hash = id; + + // Put the ID in the tab title + document.title = id; +}; + +/** + * This is a helper function for loading the page. It's required because getting the Fluid Container + * requires making async calls. + */ +export async function createContainerAndRenderInElement(element: HTMLDivElement) { + const modelLoader = new SessionStorageModelLoader( + new StaticCodeLoader(new InventoryListContainerRuntimeFactory()), + ); + let id: string; + let model: IInventoryListAppModel; + + if (location.hash.length === 0) { + // Normally our code loader is expected to match up with the version passed here. + // But since we're using a StaticCodeLoader that always loads the same runtime factory regardless, + // the version doesn't actually matter. + const createResponse = await modelLoader.createDetached("one"); + model = createResponse.model; + + // Should be the same as the uuid we generated above. + id = await createResponse.attach(); + } else { + id = location.hash.substring(1); + model = await modelLoader.loadExisting(id); + } + + const appDiv = document.createElement("div"); + const debugDiv = document.createElement("div"); + + const render = (model: IInventoryListAppModel) => { + ReactDOM.unmountComponentAtNode(appDiv); + ReactDOM.render(React.createElement(InventoryListAppView, { model }), appDiv); + + // The DebugView is just for demo purposes, to manually control code proposal and inspect the state. + ReactDOM.unmountComponentAtNode(debugDiv); + ReactDOM.render( + React.createElement(DebugView, { + model, + }), + debugDiv, + ); + }; + + // update the browser URL and the window title with the actual container ID + updateTabForId(id); + // Render it + render(model); + + element.append(appDiv, debugDiv); + + // Setting "fluidStarted" is just for our test automation + // eslint-disable-next-line @typescript-eslint/dot-notation + window["fluidStarted"] = true; +} + +/** + * For local testing we have two div's that we are rendering into independently. + */ +async function setup() { + const leftElement = document.getElementById("sbs-left") as HTMLDivElement; + if (leftElement === null) { + throw new Error("sbs-left does not exist"); + } + await createContainerAndRenderInElement(leftElement); + const rightElement = document.getElementById("sbs-right") as HTMLDivElement; + if (rightElement === null) { + throw new Error("sbs-right does not exist"); + } + await createContainerAndRenderInElement(rightElement); +} + +setup().catch((e) => { + console.error(e); + console.log( + "%cThere were issues setting up and starting the in memory Fluid Server", + "font-size:30px", + ); +}); diff --git a/examples/apps/tree-comparison/tests/inventoryList.test.ts b/examples/apps/tree-comparison/tests/inventoryList.test.ts new file mode 100644 index 000000000000..2b1d28214312 --- /dev/null +++ b/examples/apps/tree-comparison/tests/inventoryList.test.ts @@ -0,0 +1,27 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { globals } from "../jest.config"; + +// Tests disabled -- requires Tinylicious to be running, which our test environment doesn't do. +describe("inventoryList", () => { + 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); + + describe("Smoke test", () => { + beforeEach(async () => { + await page.goto(globals.PATH, { waitUntil: "load" }); + await page.waitForFunction(() => window["fluidStarted"]); + }); + + it("loads and there's an input", async () => { + // Validate the input shows up + await page.waitForSelector("input"); + }); + }); +}); diff --git a/examples/apps/tree-comparison/tsconfig.json b/examples/apps/tree-comparison/tsconfig.json new file mode 100644 index 000000000000..fdd203b1c646 --- /dev/null +++ b/examples/apps/tree-comparison/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@fluidframework/build-common/ts-common-config.json", + "compilerOptions": { + "module": "esnext", + "outDir": "lib", + "jsx": "react", + "types": ["jest", "puppeteer", "jest-environment-puppeteer", "expect-puppeteer", "react"], + }, + "include": ["src/**/*", "tests/**/*"], +} diff --git a/examples/apps/tree-comparison/webpack.config.js b/examples/apps/tree-comparison/webpack.config.js new file mode 100644 index 000000000000..0e3f5e8499e7 --- /dev/null +++ b/examples/apps/tree-comparison/webpack.config.js @@ -0,0 +1,52 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +const path = require("path"); +const { merge } = require("webpack-merge"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const webpack = require("webpack"); +// const { CleanWebpackPlugin } = require("clean-webpack-plugin"); + +module.exports = (env) => { + const isProduction = env?.production; + + return merge( + { + entry: { + start: "./src/start.ts", + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + }, + ], + }, + output: { + filename: "[name].bundle.js", + path: path.resolve(__dirname, "dist"), + library: "[name]", + // https://github.com/webpack/webpack/issues/5767 + // https://github.com/webpack/webpack/issues/7939 + devtoolNamespace: "fluid-example/app-integration-external-data", + libraryTarget: "umd", + }, + plugins: [ + new webpack.ProvidePlugin({ + process: "process/browser", + }), + new HtmlWebpackPlugin({ + template: "./src/index.html", + }), + // new CleanWebpackPlugin(), + ], + }, + isProduction ? require("./webpack.prod") : require("./webpack.dev"), + ); +}; diff --git a/examples/apps/tree-comparison/webpack.dev.js b/examples/apps/tree-comparison/webpack.dev.js new file mode 100644 index 000000000000..c5b13a83bad8 --- /dev/null +++ b/examples/apps/tree-comparison/webpack.dev.js @@ -0,0 +1,9 @@ +/*! + * 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/apps/tree-comparison/webpack.prod.js b/examples/apps/tree-comparison/webpack.prod.js new file mode 100644 index 000000000000..2273d173d964 --- /dev/null +++ b/examples/apps/tree-comparison/webpack.prod.js @@ -0,0 +1,9 @@ +/*! + * 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/apps/tree-comparison/webpack.test.js b/examples/apps/tree-comparison/webpack.test.js new file mode 100644 index 000000000000..846e11bf133a --- /dev/null +++ b/examples/apps/tree-comparison/webpack.test.js @@ -0,0 +1,55 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +const path = require("path"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const webpack = require("webpack"); + +module.exports = (env) => { + return { + entry: { + app: "./tests/index.tsx", + }, + resolve: { + extensions: [".ts", ".tsx", ".js"], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + }, + { + test: /\.css$/i, + use: ["style-loader", "css-loader"], + }, + ], + }, + output: { + filename: "[name].bundle.js", + path: path.resolve(__dirname, "dist"), + library: "[name]", + // https://github.com/webpack/webpack/issues/5767 + // https://github.com/webpack/webpack/issues/7939 + devtoolNamespace: "fluid-example/draft-js", + libraryTarget: "umd", + }, + devServer: { + static: { + directory: path.join(__dirname, "tests"), + }, + }, + plugins: [ + new webpack.ProvidePlugin({ + process: "process/browser", + }), + new HtmlWebpackPlugin({ + template: "./tests/index.html", + }), + ], + mode: "development", + devtool: "inline-source-map", + }; +}; diff --git a/packages/dds/migration-shim/src/packageVersion.ts b/packages/dds/migration-shim/src/packageVersion.ts index 06f5b3116d2b..cf472df4ae8a 100644 --- a/packages/dds/migration-shim/src/packageVersion.ts +++ b/packages/dds/migration-shim/src/packageVersion.ts @@ -6,4 +6,4 @@ */ export const pkgName = "@fluid-experimental/migration-shim"; -export const pkgVersion = "2.0.0-internal.7.1.0"; +export const pkgVersion = "2.0.0-internal.8.0.0"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 300ad6d28248..bf61f81195bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1015,6 +1015,123 @@ importers: webpack-dev-server: 4.6.0_qrfhhavll5uorjbg2snjvnp22u webpack-merge: 5.8.0 + examples/apps/tree-comparison: + specifiers: + '@fluid-example/example-utils': workspace:~ + '@fluid-experimental/react-inputs': workspace:~ + '@fluid-experimental/tree': workspace:~ + '@fluid-experimental/tree2': workspace:~ + '@fluid-internal/client-utils': workspace:~ + '@fluid-tools/build-cli': ^0.25.0 + '@fluidframework/aqueduct': workspace:~ + '@fluidframework/build-common': ^2.0.1 + '@fluidframework/build-tools': ^0.25.0 + '@fluidframework/container-definitions': workspace:~ + '@fluidframework/container-loader': workspace:~ + '@fluidframework/container-runtime-definitions': workspace:~ + '@fluidframework/core-interfaces': workspace:~ + '@fluidframework/driver-definitions': workspace:~ + '@fluidframework/driver-utils': workspace:~ + '@fluidframework/eslint-config-fluid': ^3.0.0 + '@fluidframework/request-handler': workspace:~ + '@fluidframework/routerlicious-driver': workspace:~ + '@fluidframework/runtime-utils': workspace:~ + '@fluidframework/sequence': workspace:~ + '@fluidframework/task-manager': workspace:~ + '@fluidframework/telemetry-utils': workspace:~ + '@fluidframework/test-tools': ^1.0.195075 + '@fluidframework/tinylicious-driver': workspace:~ + '@types/expect-puppeteer': 2.2.1 + '@types/jest': 29.5.3 + '@types/jest-environment-puppeteer': 2.2.0 + '@types/node': ^16.18.38 + '@types/puppeteer': 1.3.0 + '@types/react': ^17.0.44 + '@types/react-dom': ^17.0.18 + '@types/uuid': ^9.0.2 + cross-env: ^7.0.3 + css-loader: ^1.0.0 + eslint: ~8.50.0 + events: ^3.1.0 + html-webpack-plugin: ^5.5.0 + jest: ^29.6.2 + jest-junit: ^10.0.0 + jest-puppeteer: ^6.2.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 + style-loader: ^1.0.0 + tiny-typed-emitter: ^2.1.0 + ts-jest: ^29.1.1 + ts-loader: ^9.3.0 + typescript: ~5.1.6 + uuid: ^9.0.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-experimental/react-inputs': link:../../../experimental/framework/react-inputs + '@fluid-experimental/tree': link:../../../experimental/dds/tree + '@fluid-experimental/tree2': link:../../../experimental/dds/tree2 + '@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-loader': link:../../../packages/loader/container-loader + '@fluidframework/container-runtime-definitions': link:../../../packages/runtime/container-runtime-definitions + '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces + '@fluidframework/driver-definitions': link:../../../packages/common/driver-definitions + '@fluidframework/driver-utils': link:../../../packages/loader/driver-utils + '@fluidframework/request-handler': link:../../../packages/framework/request-handler + '@fluidframework/routerlicious-driver': link:../../../packages/drivers/routerlicious-driver + '@fluidframework/runtime-utils': link:../../../packages/runtime/runtime-utils + '@fluidframework/sequence': link:../../../packages/dds/sequence + '@fluidframework/task-manager': link:../../../packages/dds/task-manager + '@fluidframework/telemetry-utils': link:../../../packages/utils/telemetry-utils + '@fluidframework/tinylicious-driver': link:../../../packages/drivers/tinylicious-driver + events: 3.3.0 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + tiny-typed-emitter: 2.1.0 + uuid: 9.0.1 + devDependencies: + '@fluid-tools/build-cli': 0.25.0_ezpnjx26gdvgxsqdwiz4hmz33y + '@fluidframework/build-common': 2.0.1 + '@fluidframework/build-tools': 0.25.0_ezpnjx26gdvgxsqdwiz4hmz33y + '@fluidframework/eslint-config-fluid': 3.0.0_loebgezstcsvd2poh2d55fifke + '@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/node': 16.18.53 + '@types/puppeteer': 1.3.0 + '@types/react': 17.0.52 + '@types/react-dom': 17.0.18 + '@types/uuid': 9.0.2 + cross-env: 7.0.3 + css-loader: 1.0.1_webpack@5.88.2 + eslint: 8.50.0 + html-webpack-plugin: 5.5.1_webpack@5.88.2 + jest: 29.6.2_@types+node@16.18.53 + jest-junit: 10.0.0 + jest-puppeteer: 6.2.0_puppeteer@17.1.3 + prettier: 3.0.3 + process: 0.11.10 + puppeteer: 17.1.3 + rimraf: 4.4.1 + style-loader: 1.3.0_webpack@5.88.2 + ts-jest: 29.1.1_bd7t4cpe6hueih24owb27fpuam + ts-loader: 9.4.2_wlox7xpecxj4rvkt6b6o7frtlu + typescript: 5.1.6 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu + webpack-dev-server: 4.6.0_w3wu7rcwmvifygnqiqkxwjppse + webpack-merge: 5.8.0 + examples/benchmarks/bubblebench/baseline: specifiers: '@fluid-example/bubblebench-common': workspace:~ @@ -16420,6 +16537,63 @@ packages: - webpack-cli dev: true + /@fluid-tools/build-cli/0.25.0_ezpnjx26gdvgxsqdwiz4hmz33y: + resolution: {integrity: sha512-woQkhL+/4gcuJX69UkdxUWv4pJUM3/wmpxzDG0Lu9vBvEU4fKAdamzB5UsI4DatnaLmaet5ex06TfP8SaxNTtQ==} + engines: {node: '>=14.17.0'} + hasBin: true + dependencies: + '@fluid-tools/version-tools': 0.25.0 + '@fluidframework/build-tools': 0.25.0_ezpnjx26gdvgxsqdwiz4hmz33y + '@fluidframework/bundle-size-tools': 0.25.0_webpack-cli@4.10.0 + '@oclif/core': 2.4.0 + '@oclif/plugin-autocomplete': 2.3.8 + '@oclif/plugin-commands': 2.2.25 + '@oclif/plugin-help': 5.2.19 + '@oclif/plugin-not-found': 2.4.1 + '@oclif/plugin-plugins': 3.7.1 + '@oclif/test': 2.3.28 + '@octokit/core': 4.2.4 + '@rushstack/node-core-library': 3.60.0_@types+node@16.18.53 + async: 3.2.4 + chalk: 2.4.2 + danger: 10.9.0_@octokit+core@4.2.4 + date-fns: 2.30.0 + execa: 5.1.1 + fs-extra: 9.1.0 + globby: 11.1.0 + gray-matter: 4.0.3 + human-id: 4.0.0 + inquirer: 8.2.5 + jssm: 5.89.2 + jssm-viz-cli: 5.89.2 + latest-version: 5.1.0 + minimatch: 7.4.6 + node-fetch: 2.7.0 + npm-check-updates: 16.10.15 + oclif: 3.9.1 + prettier: 3.0.3 + prompts: 2.4.2 + read-pkg-up: 7.0.1 + semver: 7.5.4 + semver-utils: 1.1.4 + simple-git: 3.19.1 + sort-json: 2.0.1 + sort-package-json: 1.57.0 + strip-ansi: 6.0.1 + table: 6.8.1 + type-fest: 2.19.0 + transitivePeerDependencies: + - '@swc/core' + - '@types/node' + - bluebird + - encoding + - esbuild + - mem-fs + - supports-color + - uglify-js + - webpack-cli + dev: true + /@fluid-tools/fetch-tool/2.0.0-internal.7.0.0: resolution: {integrity: sha512-yKVfQbk7ujJAg6+CmMzcg8HW+kctIfrc/4KnXrvmtjGJJNfucEHtkTEQW40DjsF9+tdOAQZZViDyo/zT4yYWXw==} hasBin: true @@ -18623,11 +18797,11 @@ packages: resolution: {integrity: sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 chalk: 4.1.2 jest-message-util: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 slash: 3.0.0 dev: true @@ -18784,7 +18958,7 @@ packages: dependencies: '@jest/environment': 29.6.2 '@jest/expect': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 jest-mock: 29.6.2 transitivePeerDependencies: - supports-color @@ -18803,7 +18977,7 @@ packages: '@jest/console': 29.6.2 '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.19 '@types/node': 16.18.53 chalk: 4.1.2 @@ -18817,7 +18991,7 @@ packages: istanbul-lib-source-maps: 4.0.1 istanbul-reports: 3.1.6 jest-message-util: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-worker: 29.7.0 slash: 3.0.0 string-length: 4.0.2 @@ -18855,7 +19029,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 collect-v8-coverage: 1.0.2 dev: true @@ -18898,7 +19072,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.22.10 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.19 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 @@ -18907,7 +19081,7 @@ packages: graceful-fs: 4.2.11 jest-haste-map: 29.6.2 jest-regex-util: 29.4.3 - jest-util: 29.6.2 + jest-util: 29.7.0 micromatch: 4.0.5 pirates: 4.0.6 slash: 3.0.0 @@ -22729,7 +22903,7 @@ packages: resolution: {integrity: sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==} dependencies: '@types/estree': 1.0.1 - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 /@types/estree/0.0.51: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} @@ -22787,7 +22961,7 @@ packages: /@types/fs-extra/9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: - '@types/node': 16.18.38 + '@types/node': 16.18.53 dev: true /@types/glob/7.2.0: @@ -22925,7 +23099,6 @@ packages: /@types/json-schema/7.0.13: resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} - dev: true /@types/json-stable-stringify/1.0.34: resolution: {integrity: sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw==} @@ -23087,7 +23260,7 @@ packages: resolution: {integrity: sha512-kp1R8cTYymvYezTYWSECtSEDbxnCQaNe3i+fdsZh3dVz7umB8q6LATv0VdJp1DT0evS8YqCrFI5+DaDYJYo6Vg==} dependencies: '@types/events': 3.0.0 - '@types/node': 16.18.40 + '@types/node': 16.18.53 dev: true /@types/q/1.5.5: @@ -23584,7 +23757,7 @@ packages: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.0_eslint@8.50.0 - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 '@types/semver': 7.5.0 '@typescript-eslint/scope-manager': 5.59.11 '@typescript-eslint/types': 5.59.11 @@ -23912,7 +24085,7 @@ packages: webpack-cli: 4.x.x dependencies: webpack: 5.88.2_webpack-cli@4.10.0 - webpack-cli: 4.10.0_3zem52gpxr4nuar5zimzzagqeu + webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu dev: true /@webpack-cli/info/1.5.0_webpack-cli@4.10.0: @@ -24548,7 +24721,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 es-array-method-boxes-properly: 1.0.0 is-string: 1.0.7 @@ -24590,7 +24763,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 es-array-method-boxes-properly: 1.0.0 is-string: 1.0.7 @@ -24601,7 +24774,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 es-array-method-boxes-properly: 1.0.0 is-string: 1.0.7 @@ -28029,7 +28202,7 @@ packages: is-regex: 1.1.4 object-is: 1.1.5 object-keys: 1.1.1 - regexp.prototype.flags: 1.5.0 + regexp.prototype.flags: 1.5.1 /deep-equal/2.2.1: resolution: {integrity: sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==} @@ -28047,7 +28220,7 @@ packages: object-is: 1.1.5 object-keys: 1.1.1 object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 + regexp.prototype.flags: 1.5.1 side-channel: 1.0.4 which-boxed-primitive: 1.0.2 which-collection: 1.0.1 @@ -28112,7 +28285,6 @@ packages: get-intrinsic: 1.2.1 gopd: 1.0.1 has-property-descriptors: 1.0.0 - dev: true /define-lazy-prop/2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} @@ -28132,7 +28304,6 @@ packages: define-data-property: 1.1.0 has-property-descriptors: 1.0.0 object-keys: 1.1.1 - dev: true /define-property/0.2.5: resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==} @@ -28897,7 +29068,7 @@ packages: get-symbol-description: 1.0.0 globalthis: 1.0.3 gopd: 1.0.1 - has: 1.0.3 + has: 1.0.4 has-property-descriptors: 1.0.0 has-proto: 1.0.1 has-symbols: 1.0.3 @@ -28913,7 +29084,7 @@ packages: object-inspect: 1.12.3 object-keys: 1.1.1 object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 + regexp.prototype.flags: 1.5.1 safe-regex-test: 1.0.0 string.prototype.trim: 1.2.7 string.prototype.trimend: 1.0.6 @@ -30176,7 +30347,7 @@ packages: jest-get-type: 29.4.3 jest-matcher-utils: 29.6.2 jest-message-util: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 dev: true /express-http-proxy/1.5.1: @@ -30957,7 +31128,7 @@ packages: optional: true dependencies: '@babel/code-frame': 7.22.13 - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 @@ -30989,7 +31160,7 @@ packages: optional: true dependencies: '@babel/code-frame': 7.22.13 - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 chalk: 4.1.2 chokidar: 3.5.3 cosmiconfig: 6.0.0 @@ -32201,7 +32372,7 @@ packages: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.17.4 + terser: 5.20.0 dev: true /html-tags/3.3.1: @@ -33713,7 +33884,7 @@ packages: '@jest/environment': 29.6.2 '@jest/expect': 29.6.2 '@jest/test-result': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 chalk: 4.1.2 co: 4.6.0 @@ -33724,7 +33895,7 @@ packages: jest-message-util: 29.6.2 jest-runtime: 29.6.2 jest-snapshot: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.6.2 pure-rand: 6.0.2 @@ -33851,6 +34022,35 @@ packages: - ts-node dev: true + /jest-cli/29.6.2_@types+node@16.18.53: + resolution: {integrity: sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/test-result': 29.6.2 + '@jest/types': 29.6.1 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + jest-config: 29.6.2_@types+node@16.18.53 + jest-util: 29.6.2 + jest-validate: 29.6.2 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-config/29.6.2: resolution: {integrity: sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -33865,7 +34065,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 ci-info: 3.8.0 @@ -33878,7 +34078,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -33904,7 +34104,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.40 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 @@ -33918,7 +34118,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -33945,7 +34145,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 @@ -33959,7 +34159,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -33986,7 +34186,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.38 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 @@ -34000,7 +34200,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -34026,7 +34226,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.40 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 @@ -34040,7 +34240,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -34066,7 +34266,7 @@ packages: dependencies: '@babel/core': 7.22.10 '@jest/test-sequencer': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 babel-jest: 29.6.2_@babel+core@7.22.10 chalk: 4.1.2 @@ -34080,7 +34280,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-runner: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 @@ -34142,10 +34342,10 @@ packages: resolution: {integrity: sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.4.3 - jest-util: 29.6.2 + jest-util: 29.7.0 pretty-format: 29.6.2 dev: true @@ -34162,7 +34362,7 @@ packages: '@jest/fake-timers': 29.6.2 '@jest/types': 29.6.1 '@types/jsdom': 20.0.1 - '@types/node': 16.18.40 + '@types/node': 16.18.53 jest-mock: 29.6.2 jest-util: 29.6.2 jsdom: 20.0.3 @@ -34190,10 +34390,10 @@ packages: dependencies: '@jest/environment': 29.6.2 '@jest/fake-timers': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 jest-mock: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 dev: true /jest-environment-puppeteer/4.4.0: @@ -34257,14 +34457,14 @@ packages: resolution: {integrity: sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 '@types/node': 16.18.53 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 jest-regex-util: 29.4.3 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-worker: 29.7.0 micromatch: 4.0.5 walker: 1.0.8 @@ -34321,7 +34521,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.22.13 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 graceful-fs: 4.2.11 @@ -34401,7 +34601,7 @@ packages: graceful-fs: 4.2.11 jest-haste-map: 29.6.2 jest-pnp-resolver: 1.2.3_jest-resolve@29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-validate: 29.6.2 resolve: 1.22.6 resolve.exports: 2.0.2 @@ -34416,7 +34616,7 @@ packages: '@jest/environment': 29.6.2 '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 chalk: 4.1.2 emittery: 0.13.1 @@ -34428,7 +34628,7 @@ packages: jest-message-util: 29.6.2 jest-resolve: 29.6.2 jest-runtime: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 jest-watcher: 29.6.2 jest-worker: 29.7.0 p-limit: 3.1.0 @@ -34447,7 +34647,7 @@ packages: '@jest/source-map': 29.6.0 '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 chalk: 4.1.2 cjs-module-lexer: 1.2.3 @@ -34460,7 +34660,7 @@ packages: jest-regex-util: 29.4.3 jest-resolve: 29.6.2 jest-snapshot: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: @@ -34486,7 +34686,7 @@ packages: '@babel/types': 7.22.10 '@jest/expect-utils': 29.6.2 '@jest/transform': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 babel-preset-current-node-syntax: 1.0.1_@babel+core@7.22.10 chalk: 4.1.2 expect: 29.6.2 @@ -34495,7 +34695,7 @@ packages: jest-get-type: 29.4.3 jest-matcher-utils: 29.6.2 jest-message-util: 29.6.2 - jest-util: 29.6.2 + jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.6.2 semver: 7.5.4 @@ -34571,7 +34771,7 @@ packages: resolution: {integrity: sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 29.4.3 @@ -34584,12 +34784,12 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.6.2 - '@jest/types': 29.6.1 + '@jest/types': 29.6.3 '@types/node': 16.18.53 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 29.6.2 + jest-util: 29.7.0 string-length: 4.0.2 dev: true @@ -34704,6 +34904,27 @@ packages: - ts-node dev: true + /jest/29.6.2_@types+node@16.18.53: + resolution: {integrity: sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.6.2 + '@jest/types': 29.6.1 + import-local: 3.1.0 + jest-cli: 29.6.2_@types+node@16.18.53 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jju/1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true @@ -37835,7 +38056,7 @@ packages: dependencies: array.prototype.reduce: 1.0.5 call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 safe-array-concat: 1.0.0 dev: true @@ -39263,7 +39484,7 @@ packages: resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@jest/schemas': 29.6.0 + '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.2.0 dev: true @@ -39369,7 +39590,7 @@ packages: dependencies: array.prototype.map: 1.0.5 call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 get-intrinsic: 1.2.1 iterate-value: 1.0.2 @@ -39380,7 +39601,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 dev: true @@ -40589,8 +40810,9 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 functions-have-names: 1.2.3 + dev: true /regexp.prototype.flags/1.5.1: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} @@ -40599,7 +40821,6 @@ packages: call-bind: 1.0.2 define-properties: 1.2.1 set-function-name: 2.0.1 - dev: true /regexpp/2.0.1: resolution: {integrity: sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==} @@ -41355,7 +41576,7 @@ packages: resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} engines: {node: '>= 8.9.0'} dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 ajv: 6.12.6 ajv-keywords: 3.5.2_ajv@6.12.6 dev: true @@ -41364,7 +41585,7 @@ packages: resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==} engines: {node: '>= 8.9.0'} dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 ajv: 6.12.6 ajv-keywords: 3.5.2_ajv@6.12.6 @@ -41380,7 +41601,7 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 ajv: 6.12.6 ajv-keywords: 3.5.2_ajv@6.12.6 @@ -41388,7 +41609,7 @@ packages: resolution: {integrity: sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ==} engines: {node: '>= 12.13.0'} dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.13 ajv: 8.12.0 ajv-formats: 2.1.1 ajv-keywords: 5.1.0_ajv@8.12.0 @@ -41594,7 +41815,6 @@ packages: define-data-property: 1.1.0 functions-have-names: 1.2.3 has-property-descriptors: 1.0.0 - dev: true /set-value/2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} @@ -42543,7 +42763,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 dev: true @@ -42569,7 +42789,7 @@ packages: resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 dev: true @@ -42585,7 +42805,7 @@ packages: resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 + define-properties: 1.2.1 es-abstract: 1.21.2 dev: true @@ -43191,7 +43411,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 terser: 5.20.0 - webpack: 5.88.2 + webpack: 5.88.2_webpack-cli@4.10.0 dev: true /terser/4.8.1: @@ -43322,6 +43542,10 @@ packages: next-tick: 1.1.0 dev: true + /tiny-typed-emitter/2.1.0: + resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + dev: false + /tiny-warning/1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false @@ -44639,7 +44863,7 @@ packages: /util.promisify/1.0.0: resolution: {integrity: sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==} dependencies: - define-properties: 1.2.0 + define-properties: 1.2.1 object.getownpropertydescriptors: 2.1.6 dev: true @@ -45166,6 +45390,42 @@ packages: webpack-merge: 5.8.0 dev: true + /webpack-cli/4.10.0_ai6cu5vnuisb2akyozxbiaqwvu: + resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + '@webpack-cli/generators': '*' + '@webpack-cli/migrate': '*' + webpack: 4.x.x || 5.x.x || ^5.82.0 + webpack-bundle-analyzer: '*' + webpack-dev-server: '*' + peerDependenciesMeta: + '@webpack-cli/generators': + optional: true + '@webpack-cli/migrate': + optional: true + webpack-bundle-analyzer: + optional: true + webpack-dev-server: + optional: true + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@webpack-cli/configtest': 1.2.0_w3wu7rcwmvifygnqiqkxwjppse + '@webpack-cli/info': 1.5.0_webpack-cli@4.10.0 + '@webpack-cli/serve': 1.7.0_hxwqhfj3xkkgp5omnyg2m7pnsm + colorette: 2.0.20 + commander: 7.2.0 + cross-spawn: 7.0.3 + fastest-levenshtein: 1.0.16 + import-local: 3.1.0 + interpret: 2.2.0 + rechoir: 0.7.1 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-dev-server: 4.6.0_w3wu7rcwmvifygnqiqkxwjppse + webpack-merge: 5.8.0 + dev: true + /webpack-cli/4.10.0_dfe3q2c2dnof5cignr4xxddwka: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} @@ -45464,6 +45724,52 @@ packages: - supports-color - utf-8-validate + /webpack-dev-server/4.6.0_w3wu7rcwmvifygnqiqkxwjppse: + resolution: {integrity: sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 || ^5.82.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + ansi-html-community: 0.0.8 + bonjour: 3.5.0 + chokidar: 3.5.3 + colorette: 2.0.20 + compression: 1.7.4 + connect-history-api-fallback: 1.6.0 + default-gateway: 6.0.3 + del: 6.1.1 + express: 4.18.2 + graceful-fs: 4.2.11 + html-entities: 2.3.3 + http-proxy-middleware: 2.0.6 + ipaddr.js: 2.0.1 + open: 8.4.2 + p-retry: 4.6.2 + portfinder: 1.0.32 + schema-utils: 4.0.1 + selfsigned: 1.10.14 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + strip-ansi: 7.1.0 + url: 0.11.3 + webpack: 5.88.2_webpack-cli@4.10.0 + webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu + webpack-dev-middleware: 5.3.3_webpack@5.88.2 + ws: 8.14.2 + transitivePeerDependencies: + - '@types/express' + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: true + /webpack-filter-warnings-plugin/1.2.1_webpack@4.47.0: resolution: {integrity: sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==} engines: {node: '>= 4.3 < 5.0.0 || >= 5.10'} From fa7b085ef044d573de62bf3b035baa9c63741b88 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:27:05 -0700 Subject: [PATCH 12/50] fix(merge-tree sequence): rb tree crash (#17395) Resolves a crash in our red-black tree implementation that occurs when multiple references slide off the tree at once. [ADO#4477](https://dev.azure.com/fluidframework/internal/_workitems/edit/4477) --- .../merge-tree/api-report/merge-tree.api.md | 4 +- packages/dds/merge-tree/package.json | 15 +++ packages/dds/merge-tree/src/localReference.ts | 59 ++++++------ .../src/test/client.localReference.spec.ts | 91 ++++++++++++++++++- .../test/client.localReferenceFarm.spec.ts | 11 ++- .../merge-tree/src/test/revertibles.spec.ts | 12 ++- packages/dds/merge-tree/src/test/testUtils.ts | 12 +++ .../validateMergeTreePrevious.generated.ts | 5 + packages/dds/sequence/src/revertibles.ts | 12 ++- packages/dds/sequence/src/sequence.ts | 11 ++- .../src/test/intervalCollection.fuzz.spec.ts | 7 +- .../src/test/intervalCollection.spec.ts | 8 +- .../dds/sequence/src/test/reentrancy.spec.ts | 80 +++++++++++++++- 13 files changed, 270 insertions(+), 57 deletions(-) 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 673e2a0b1e21..b684237d91bd 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -630,14 +630,12 @@ export class LocalReferenceCollection { // @internal append(other: LocalReferenceCollection): void; // @internal (undocumented) - clear(): void; - // @internal (undocumented) createLocalRef(offset: number, refType: ReferenceType, properties: PropertySet | undefined, slidingPreference?: SlidingPreference, canSlideToEndpoint?: boolean): LocalReferencePosition; // @internal (undocumented) get empty(): boolean; // @internal has(lref: ReferencePosition): boolean; - // @internal (undocumented) + // @internal hierRefCount: number; // @internal (undocumented) isAfterTombstone(lref: LocalReferencePosition): boolean; diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index 056f6b2e26ff..533f0358b9dc 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -156,6 +156,21 @@ "RemovedFunctionDeclaration_extendIfUndefined": { "forwardCompat": false, "backCompat": false + }, + "ClassDeclaration_LocalReferenceCollection": { + "backCompat": false + }, + "ClassDeclaration_BaseSegment": { + "backCompat": false + }, + "InterfaceDeclaration_IConsensusInfo": { + "backCompat": false + }, + "ClassDeclaration_Marker": { + "backCompat": false + }, + "ClassDeclaration_TextSegment": { + "backCompat": false } } } diff --git a/packages/dds/merge-tree/src/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index ad69db848675..6832ce9c31e3 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -33,9 +33,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)) { @@ -194,7 +191,18 @@ export function* filterLocalReferencePositions( } /** - * Represents a collection of {@link LocalReferencePosition}s associated with one segment in a merge-tree. + * 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. */ export class LocalReferenceCollection { public static append(seg1: ISegment, seg2: ISegment) { @@ -212,10 +220,15 @@ 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. + * The number of references whose reference type is one of the hierarchical + * reference types, currently only {@link ReferenceType.Tile}. + * + * @remarks This field should only be accessed by mergeTree. * @internal */ public hierRefCount: number = 0; @@ -277,36 +290,12 @@ export class LocalReferenceCollection { return iterator; } - /** - * @remarks This method should only be called by mergeTree. - * @internal - */ - 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. * @internal */ public get empty() { + validateRefCount?.(this); return this.refCount === 0; } @@ -326,6 +315,7 @@ export class LocalReferenceCollection { if (!refTypeIncludesFlag(ref, ReferenceType.Transient)) { this.addLocalRef(ref, offset); } + validateRefCount?.(this); return ref; } @@ -354,6 +344,7 @@ export class LocalReferenceCollection { } this.refCount++; } + validateRefCount?.(this); } /** @@ -367,11 +358,13 @@ export class LocalReferenceCollection { const node = lref.getListNode(); node?.list?.remove(node); - lref.link(lref.getSegment(), lref.getOffset(), undefined); + lref.link(undefined, 0, undefined); + if (refHasTileLabels(lref)) { this.hierRefCount--; } this.refCount--; + validateRefCount?.(this); return lref; } } @@ -406,6 +399,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. @@ -478,6 +472,7 @@ export class LocalReferenceCollection { // shrink the offset array when empty and splitting this.refsByOffset.length = offset; } + validateRefCount?.(this); } /** @@ -515,6 +510,7 @@ export class LocalReferenceCollection { } } } + validateRefCount?.(this); } /** * @remarks This method should only be called by mergeTree. @@ -548,6 +544,7 @@ export class LocalReferenceCollection { } } } + validateRefCount?.(this); } /** 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 b88ae9cc0a78..cc7dd05fe410 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-internal/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/revertibles.spec.ts b/packages/dds/merge-tree/src/test/revertibles.spec.ts index 4e9c634f01c4..54c0ab4aeb63 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(); diff --git a/packages/dds/merge-tree/src/test/testUtils.ts b/packages/dds/merge-tree/src/test/testUtils.ts index f83a739fa84f..90c513909391 100644 --- a/packages/dds/merge-tree/src/test/testUtils.ts +++ b/packages/dds/merge-tree/src/test/testUtils.ts @@ -12,6 +12,7 @@ import { ReferenceType } from "../ops"; import { PropertySet } from "../properties"; import { MergeTree } from "../mergeTree"; import { walkAllChildSegments } from "../mergeTreeNodeWalk"; +import { LocalReferenceCollection } from "../localReference"; import { loadText } from "./text"; export function loadTextFromFile(filename: string, mergeTree: MergeTree, segLimit = 0) { @@ -246,3 +247,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/types/validateMergeTreePrevious.generated.ts b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts index 0242e58a4fe4..44832a39c7fd 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); use_old_ClassDeclaration_BaseSegment( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_BaseSegment()); /* @@ -308,6 +309,7 @@ declare function get_current_InterfaceDeclaration_IConsensusInfo(): declare function use_old_InterfaceDeclaration_IConsensusInfo( use: TypeOnly); use_old_InterfaceDeclaration_IConsensusInfo( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_IConsensusInfo()); /* @@ -1088,6 +1090,7 @@ declare function get_current_ClassDeclaration_LocalReferenceCollection(): declare function use_old_ClassDeclaration_LocalReferenceCollection( use: TypeOnly); use_old_ClassDeclaration_LocalReferenceCollection( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_LocalReferenceCollection()); /* @@ -1160,6 +1163,7 @@ declare function get_current_ClassDeclaration_Marker(): declare function use_old_ClassDeclaration_Marker( use: TypeOnly); use_old_ClassDeclaration_Marker( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_Marker()); /* @@ -2072,6 +2076,7 @@ declare function get_current_ClassDeclaration_TextSegment(): declare function use_old_ClassDeclaration_TextSegment( use: TypeOnly); use_old_ClassDeclaration_TextSegment( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_TextSegment()); /* diff --git a/packages/dds/sequence/src/revertibles.ts b/packages/dds/sequence/src/revertibles.ts index 06867a0f6e1e..35f45996f16c 100644 --- a/packages/dds/sequence/src/revertibles.ts +++ b/packages/dds/sequence/src/revertibles.ts @@ -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 diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 1a8caa78514a..d8db39f8367c 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -426,12 +426,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. * - * @param handler - The function to handle each segment + * Set split range to true to ensure only segments within the range are walked. + * + * @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 diff --git a/packages/dds/sequence/src/test/intervalCollection.fuzz.spec.ts b/packages/dds/sequence/src/test/intervalCollection.fuzz.spec.ts index 30162c7706d0..e5eb2658aec7 100644 --- a/packages/dds/sequence/src/test/intervalCollection.fuzz.spec.ts +++ b/packages/dds/sequence/src/test/intervalCollection.fuzz.spec.ts @@ -293,8 +293,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 @@ -302,7 +300,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, @@ -340,8 +338,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, numberOfClients: 3, diff --git a/packages/dds/sequence/src/test/intervalCollection.spec.ts b/packages/dds/sequence/src/test/intervalCollection.spec.ts index b09f037876cf..88b08d6cd8e7 100644 --- a/packages/dds/sequence/src/test/intervalCollection.spec.ts +++ b/packages/dds/sequence/src/test/intervalCollection.spec.ts @@ -216,9 +216,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/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) => { From 8f6e82eb28bf6f14467ad54a20c55dd8012c602e Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:06:20 -0400 Subject: [PATCH 13/50] refactor(merge-tree): remove special handling of tile references (#17830) Removes `hierRefCount` and special code that updates path lengths in the case that a segment has a reference with the reftype of Tile. Only markers should have such special handling. --- .../merge-tree/api-report/merge-tree.api.md | 2 - packages/dds/merge-tree/src/localReference.ts | 28 +------ packages/dds/merge-tree/src/mergeTree.ts | 75 +++---------------- 3 files changed, 12 insertions(+), 93 deletions(-) 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 b684237d91bd..e23e5f3b0782 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -635,8 +635,6 @@ export class LocalReferenceCollection { get empty(): boolean; // @internal has(lref: ReferencePosition): boolean; - // @internal - hierRefCount: number; // @internal (undocumented) isAfterTombstone(lref: LocalReferencePosition): boolean; // @internal (undocumented) diff --git a/packages/dds/merge-tree/src/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index 6832ce9c31e3..460574dcd078 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -10,7 +10,7 @@ import { ISegment } from "./mergeTreeNodes"; import { TrackingGroup, TrackingGroupCollection } from "./mergeTreeTracking"; import { ICombiningOp, ReferenceType } from "./ops"; import { addProperties, PropertySet } from "./properties"; -import { refHasTileLabels, ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; +import { ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; /** * Dictates the preferential direction for a {@link ReferencePosition} to slide @@ -224,14 +224,6 @@ export class LocalReferenceCollection { validateRefCount?.(seg2.localRefs); } - /** - * The number of references whose reference type is one of the hierarchical - * reference types, currently only {@link ReferenceType.Tile}. - * - * @remarks This field should only be accessed by mergeTree. - * @internal - */ - public hierRefCount: number = 0; private readonly refsByOffset: (IRefsAtOffset | undefined)[]; private refCount: number = 0; @@ -339,9 +331,6 @@ export class LocalReferenceCollection { lref.link(this.segment, offset, atRefs.push(lref).last); - if (refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; } validateRefCount?.(this); @@ -360,9 +349,6 @@ export class LocalReferenceCollection { lref.link(undefined, 0, undefined); - if (refHasTileLabels(lref)) { - this.hierRefCount--; - } this.refCount--; validateRefCount?.(this); return lref; @@ -385,9 +371,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); @@ -461,10 +445,6 @@ export class LocalReferenceCollection { for (const lref of localRefs) { assertLocalReferences(lref); lref.link(splitSeg, lref.getOffset() - offset, lref.getListNode()); - if (refHasTileLabels(lref)) { - this.hierRefCount--; - localRefs.hierRefCount++; - } this.refCount--; localRefs.refCount++; } @@ -500,9 +480,6 @@ export class LocalReferenceCollection { ? beforeRefs.unshift(lref)?.first : beforeRefs.insertAfter(precedingRef, lref)?.first; lref.link(this.segment, 0, precedingRef); - if (refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; lref.callbacks?.afterSlide?.(lref); } else { @@ -534,9 +511,6 @@ export class LocalReferenceCollection { lref.callbacks?.beforeSlide?.(lref); afterRefs.push(lref); lref.link(this.segment, lastOffset, afterRefs.last); - if (refHasTileLabels(lref)) { - this.hierRefCount++; - } this.refCount++; lref.callbacks?.afterSlide?.(lref); } else { diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 529bf2dc4c6d..2c7b4f839def 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -11,7 +11,6 @@ import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils import { IAttributionCollectionSerializer } from "./attributionCollection"; import { Comparer, Heap, List, ListNode } from "./collections"; import { - LocalClientId, NonCollabClient, TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, @@ -25,7 +24,6 @@ import { SlidingPreference, } from "./localReference"; import { - BaseSegment, BlockAction, CollaborationWindow, IHierBlock, @@ -194,15 +192,6 @@ function addTileIfNotPresent(tile: ReferencePosition, tiles: object) { } } -/** - * 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.Tile; - function addNodeReferences( mergeTree: MergeTree, node: IMergeNode, @@ -211,31 +200,16 @@ function addNodeReferences( ) { 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); - } - } else { - const baseSegment = node as BaseSegment; - if ( - 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 ((mergeTree.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) { + mergeTree.mapIdToSegment(markerId, segment); + } + if (refTypeIncludesFlag(segment, ReferenceType.Tile)) { + addTile(segment, rightmostTiles); + addTileIfNotPresent(segment, leftmostTiles); } } } else { @@ -869,16 +843,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 = ( @@ -2162,17 +2126,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); @@ -2219,13 +2173,6 @@ export class MergeTree { canSlideToEndpoint, ); - if (refTypeIncludesFlag(refType, hierRefTypes)) { - this.blockUpdatePathLengths( - segment.parent, - TreeMaintenanceSequenceNumber, - LocalClientId, - ); - } return segRef; } From 00cfc0045477fa4a3eb1e001f5ac30b64c1537ef Mon Sep 17 00:00:00 2001 From: jzaffiro <110866475+jzaffiro@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:02:01 -0700 Subject: [PATCH 14/50] Remove the findTile API (#17997) Complete the removal of `findTile`, which was started in https://github.com/microsoft/FluidFramework/pull/16517 and https://github.com/microsoft/FluidFramework/pull/17832. Open questions: 1. Can I remove the free functions/interface in mergeTree (`IReferenceSearchInfo`, `recordTileStart`, and `tileShift`) without deprecating them first, as they were only used in `findTile`? 2. Can I move the sole `annotateMarker` test left in `client.spec.ts` into a different file, or should the existing file be renamed and have more tests added? [AB#5536](https://dev.azure.com/fluidframework/internal/_workitems/edit/5536) --- .../webflow/src/document/index.ts | 22 +- .../merge-tree/api-report/merge-tree.api.md | 5 - packages/dds/merge-tree/src/client.ts | 8 - packages/dds/merge-tree/src/mergeTree.ts | 223 ----------------- packages/dds/merge-tree/src/ops.ts | 2 +- .../src/test/client.annotateMarker.spec.ts | 46 ++++ .../dds/merge-tree/src/test/client.spec.ts | 224 ------------------ .../dds/merge-tree/src/test/wordUnitTests.ts | 8 +- .../dds/sequence/api-report/sequence.api.md | 5 - packages/dds/sequence/src/sharedString.ts | 23 -- 10 files changed, 62 insertions(+), 504 deletions(-) create mode 100644 packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts delete mode 100644 packages/dds/merge-tree/src/test/client.spec.ts diff --git a/examples/data-objects/webflow/src/document/index.ts b/examples/data-objects/webflow/src/document/index.ts index b2b44260a522..15f59a1c6ae0 100644 --- a/examples/data-objects/webflow/src/document/index.ts +++ b/examples/data-objects/webflow/src/document/index.ts @@ -414,20 +414,20 @@ export class FlowDocument extends LazyLoadedDataObject { // (undocumented) createTextHelper(): IMergeTreeTextHelper; findReconnectionPosition(segment: ISegment, localSeq: number): number; - // @deprecated (undocumented) - findTile(startPos: number, tileLabel: string, preceding?: boolean): { - tile: ReferencePosition; - pos: number; - } | undefined; // (undocumented) getClientId(): number; // (undocumented) diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index 5064cd4dc71f..df6a2edde967 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -1163,14 +1163,6 @@ export class Client extends TypedEventEmitter { } } - /** - * @deprecated Use searchForMarker instead. - */ - findTile(startPos: number, tileLabel: string, preceding = true) { - const clientId = this.getClientId(); - return this._mergeTree.findTile(startPos, clientId, tileLabel, preceding); - } - /** * Searches a string for the nearest marker in either direction to a given start position. * The search will include the start position, so markers at the start position are valid diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 2c7b4f839def..a64f7856dd1a 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -40,7 +40,6 @@ import { MergeBlock, MinListener, reservedMarkerIdKey, - SegmentActions, SegmentGroup, seqLTE, toRemovalInfo, @@ -119,59 +118,6 @@ const LRUSegmentComparer: Comparer = { compare: (a, b) => a.maxSeq - b.maxSeq, }; -interface IReferenceSearchInfo { - mergeTree: MergeTree; - tileLabel: string; - tilePrecedesPos?: boolean; - tile?: ReferencePosition; -} - -function recordTileStart( - segment: ISegment, - segpos: number, - refSeq: number, - clientId: number, - start: number, - end: number, - searchInfo: IReferenceSearchInfo, -) { - if (Marker.is(segment)) { - if (refHasTileLabel(segment, searchInfo.tileLabel)) { - searchInfo.tile = segment; - } - } - return false; -} - -function tileShift( - node: IMergeNode, - segpos: number, - refSeq: number, - clientId: number, - offset: number | undefined, - end: number | undefined, - searchInfo: IReferenceSearchInfo, -) { - if (node.isLeaf()) { - const seg = node; - if ((searchInfo.mergeTree.localNetLength(seg) ?? 0) > 0 && Marker.is(seg)) { - if (refHasTileLabel(seg, searchInfo.tileLabel)) { - searchInfo.tile = seg; - } - } - } else { - const block = node as IHierBlock; - const marker = searchInfo.tilePrecedesPos - ? 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) { const tileLabels = refGetTileLabels(tile); if (tileLabels) { @@ -1090,60 +1036,6 @@ export class MergeTree { return DetachedReferencePosition; } - // TODO: filter function - /** - * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`. - * @deprecated Use searchForMarker instead. - * - * @param startPos - Position at which to start the search - * @param clientId - clientId dictating the perspective to search from - * @param tileLabel - Label of the tile to search for - * @param tilePrecedesPos - Whether the desired tile comes before (true) or after (false) `startPos` - */ - public findTile(startPos: number, clientId: number, tileLabel: string, tilePrecedesPos = true) { - const searchInfo: IReferenceSearchInfo = { - mergeTree: this, - tilePrecedesPos, - tileLabel, - }; - - if (tilePrecedesPos) { - this.search( - startPos, - UniversalSequenceNumber, - clientId, - { leaf: recordTileStart, shift: tileShift }, - searchInfo, - ); - } else { - this.backwardSearch( - startPos, - UniversalSequenceNumber, - clientId, - { leaf: recordTileStart, shift: tileShift }, - searchInfo, - ); - } - - if (searchInfo.tile) { - let pos: number; - if (searchInfo.tile.isLeaf()) { - const marker = searchInfo.tile as Marker; - pos = this.getPosition(marker, UniversalSequenceNumber, clientId); - } else { - const localRef = searchInfo.tile; - pos = this.referencePositionToLocalPosition( - localRef, - UniversalSequenceNumber, - clientId, - ); - } - return { tile: searchInfo.tile, pos }; - } - - return undefined; - } - /** * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `forwards`. * Uses depthFirstNodeWalk in addition to block-accelerated functionality. The search position will be included in @@ -1201,121 +1093,6 @@ export class MergeTree { return foundMarker; } - private search( - pos: number, - refSeq: number, - clientId: number, - actions: SegmentActions | undefined, - clientData: TClientData, - ): ISegment | undefined { - return this.searchBlock(this.root, pos, 0, refSeq, clientId, actions, clientData); - } - - private searchBlock( - block: IMergeBlock, - pos: number, - segpos: number, - refSeq: number, - clientId: number, - actions: SegmentActions | undefined, - clientData: TClientData, - localSeq?: number, - ): ISegment | undefined { - const { pre, post, contains, leaf, shift } = actions ?? {}; - let _pos = pos; - let _segpos = segpos; - const children = block.children; - pre?.(block, _segpos, refSeq, clientId, undefined, undefined, clientData); - for (let childIndex = 0; childIndex < block.childCount; childIndex++) { - const child = children[childIndex]; - const len = this.nodeLength(child, refSeq, clientId, localSeq) ?? 0; - if ( - (!contains && _pos < len) || - contains?.(child, _pos, refSeq, clientId, undefined, undefined, clientData) - ) { - // Found entry containing pos - if (!child.isLeaf()) { - return this.searchBlock( - child, - _pos, - _segpos, - refSeq, - clientId, - actions, - clientData, - localSeq, - ); - } else { - leaf?.(child, _segpos, refSeq, clientId, _pos, -1, clientData); - return child; - } - } else { - shift?.(child, _segpos, refSeq, clientId, _pos, undefined, clientData); - _pos -= len; - _segpos += len; - } - } - post?.(block, _segpos, refSeq, clientId, undefined, undefined, clientData); - } - - private backwardSearch( - pos: number, - refSeq: number, - clientId: number, - actions: SegmentActions | undefined, - clientData: TClientData, - ): ISegment | undefined { - const len = this.getLength(refSeq, clientId); - if (pos > len) { - return undefined; - } - return this.backwardSearchBlock(this.root, pos, len, refSeq, clientId, actions, clientData); - } - - private backwardSearchBlock( - block: IMergeBlock, - pos: number, - segEnd: number, - refSeq: number, - clientId: number, - actions: SegmentActions | undefined, - clientData: TClientData, - ): ISegment | undefined { - const { pre, post, contains, leaf, shift } = actions ?? {}; - let _segEnd = segEnd; - const children = block.children; - pre?.(block, _segEnd, refSeq, clientId, undefined, undefined, clientData); - for (let childIndex = block.childCount - 1; childIndex >= 0; childIndex--) { - const child = children[childIndex]; - const len = this.nodeLength(child, refSeq, clientId) ?? 0; - const segpos = _segEnd - len; - if ( - (!contains && pos >= segpos) || - contains?.(child, pos, refSeq, clientId, undefined, undefined, clientData) - ) { - // Found entry containing pos - if (!child.isLeaf()) { - return this.backwardSearchBlock( - child, - pos, - _segEnd, - refSeq, - clientId, - actions, - clientData, - ); - } else { - leaf?.(child, segpos, refSeq, clientId, pos, -1, clientData); - return child; - } - } else { - shift?.(child, segpos, refSeq, clientId, pos, undefined, clientData); - _segEnd = segpos; - } - } - post?.(block, _segEnd, refSeq, clientId, undefined, undefined, clientData); - } - private updateRoot(splitNode: IMergeBlock | undefined) { if (splitNode !== undefined) { const newRoot = this.makeBlock(2); diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index 614fb8b7d138..5bd9a3cef237 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -9,7 +9,7 @@ export enum ReferenceType { Simple = 0x0, /** - * Allows this reference to be located using the `findTile` API on merge-tree. + * Allows this reference to be located using the `searchForMarker` API on merge-tree. */ Tile = 0x1, RangeBegin = 0x10, diff --git a/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts b/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts new file mode 100644 index 000000000000..1d0a3bd87969 --- /dev/null +++ b/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts @@ -0,0 +1,46 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "assert"; +import { UniversalSequenceNumber } from "../constants"; +import { Marker, reservedMarkerIdKey } from "../mergeTreeNodes"; +import { ReferenceType } from "../ops"; +import { TextSegment } from "../textSegment"; +import { TestClient } from "./testClient"; +import { insertSegments } from "./testUtils"; + +describe("TestClient", () => { + const localUserLongId = "localUser"; + let client: TestClient; + + beforeEach(() => { + client = new TestClient(); + insertSegments({ + mergeTree: client.mergeTree, + pos: 0, + segments: [TextSegment.make("")], + refSeq: UniversalSequenceNumber, + clientId: client.getClientId(), + seq: UniversalSequenceNumber, + opArgs: undefined, + }); + client.startOrUpdateCollaboration(localUserLongId); + }); + + describe(".annotateMarker", () => { + it("annotate valid marker", () => { + const insertOp = client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedMarkerIdKey]: "123", + }); + assert(insertOp); + const markerInfo = client.getContainingSegment(0); + const marker = markerInfo.segment as Marker; + const annotateOp = client.annotateMarker(marker, { foo: "bar" }, undefined); + assert(annotateOp); + assert(marker.properties); + assert(marker.properties.foo, "bar"); + }); + }); +}); diff --git a/packages/dds/merge-tree/src/test/client.spec.ts b/packages/dds/merge-tree/src/test/client.spec.ts deleted file mode 100644 index f5ccd40b8e02..000000000000 --- a/packages/dds/merge-tree/src/test/client.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { strict as assert } from "assert"; -import { UniversalSequenceNumber } from "../constants"; -import { Marker, reservedMarkerIdKey } from "../mergeTreeNodes"; -import { ReferenceType } from "../ops"; -import { reservedTileLabelsKey } from "../referencePositions"; -import { TextSegment } from "../textSegment"; -import { TestClient } from "./testClient"; -import { insertSegments } from "./testUtils"; - -describe("TestClient", () => { - const localUserLongId = "localUser"; - let client: TestClient; - - beforeEach(() => { - client = new TestClient(); - insertSegments({ - mergeTree: client.mergeTree, - pos: 0, - segments: [TextSegment.make("")], - refSeq: UniversalSequenceNumber, - clientId: client.getClientId(), - seq: UniversalSequenceNumber, - opArgs: undefined, - }); - client.startOrUpdateCollaboration(localUserLongId); - }); - - describe(".findTile", () => { - it("Should be able to find non preceding tile based on label", () => { - const tileLabel = "EOP"; - - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(0, "abc"); - - console.log(client.getText()); - - assert.equal(client.getLength(), 4, "length not expected"); - - const tile = client.findTile(0, tileLabel, false); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 3, "Tile with label not at expected position"); - }); - - it("Should be able to find non preceding tile position based on label from client with single tile", () => { - const tileLabel = "EOP"; - client.insertTextLocal(0, "abc d"); - - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - console.log(client.getText()); - - assert.equal(client.getLength(), 6, "length not expected"); - - const tile = client.findTile(0, tileLabel, false); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 0, "Tile with label not at expected position"); - }); - - it("Should be able to find preceding tile position based on label from client with multiple tile", () => { - const tileLabel = "EOP"; - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(0, "abc d"); - - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(7, "ef"); - client.insertMarkerLocal(8, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - console.log(client.getText()); - - assert.equal(client.getLength(), 10, "length not expected"); - - const tile = client.findTile(5, tileLabel); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 0, "Tile with label not at expected position"); - }); - - it("Should be able to find non preceding tile position from client with multiple tile", () => { - const tileLabel = "EOP"; - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(0, "abc d"); - - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(7, "ef"); - client.insertMarkerLocal(8, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - console.log(client.getText()); - - assert.equal(client.getLength(), 10, "length not expected"); - - const tile = client.findTile(5, tileLabel, false); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 6, "Tile with label not at expected position"); - }); - - it("Should be able to find tile from client with text length 1", () => { - const tileLabel = "EOP"; - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - console.log(client.getText()); - - assert.equal(client.getLength(), 1, "length not expected"); - - const tile = client.findTile(client.getLength(), tileLabel); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 0, "Tile with label not at expected position"); - - const tile1 = client.findTile(0, tileLabel, false); - - assert(tile1, "Returned tile undefined."); - - assert.equal(tile1.pos, 0, "Tile with label not at expected position"); - }); - - it("Should be able to find only preceding but not non preceding tile with index out of bound", () => { - const tileLabel = "EOP"; - client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedTileLabelsKey]: [tileLabel], - [reservedMarkerIdKey]: "some-id", - }); - - client.insertTextLocal(0, "abc"); - console.log(client.getText()); - - assert.equal(client.getLength(), 4, "length not expected"); - - const tile = client.findTile(5, tileLabel); - - assert(tile, "Returned tile undefined."); - - assert.equal(tile.pos, 3, "Tile with label not at expected position"); - - const tile1 = client.findTile(5, tileLabel, false); - - assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); - }); - - it("Should return undefined when trying to find tile from text without the specified tile", () => { - const tileLabel = "EOP"; - client.insertTextLocal(0, "abc"); - console.log(client.getText()); - - assert.equal(client.getLength(), 3, "length not expected"); - - const tile = client.findTile(1, tileLabel); - - assert.equal(typeof tile, "undefined", "Returned tile should be undefined."); - - const tile1 = client.findTile(1, tileLabel, false); - - assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); - }); - - it("Should return undefined when trying to find tile from null text", () => { - const tileLabel = "EOP"; - - const tile = client.findTile(1, tileLabel); - - assert.equal(typeof tile, "undefined", "Returned tile should be undefined."); - - const tile1 = client.findTile(1, tileLabel, false); - - assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); - }); - }); - - describe(".annotateMarker", () => { - it("annotate valid marker", () => { - const insertOp = client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedMarkerIdKey]: "123", - }); - assert(insertOp); - const markerInfo = client.getContainingSegment(0); - const marker = markerInfo.segment as Marker; - const annotateOp = client.annotateMarker(marker, { foo: "bar" }, undefined); - assert(annotateOp); - assert(marker.properties); - assert(marker.properties.foo, "bar"); - }); - }); -}); diff --git a/packages/dds/merge-tree/src/test/wordUnitTests.ts b/packages/dds/merge-tree/src/test/wordUnitTests.ts index 3bb01df23d94..29c0df60ac75 100644 --- a/packages/dds/merge-tree/src/test/wordUnitTests.ts +++ b/packages/dds/merge-tree/src/test/wordUnitTests.ts @@ -156,16 +156,16 @@ function measureFetch(startFile: string, withBookmarks = false) { let count = 0; for (let i = 0; i < reps; i++) { for (let pos = 0; pos < client.getLength(); ) { - // let prevPG = client.findTile(pos, "pg"); + // let prevPG = client.searchForMarker(pos, "pg", false); // let caBegin: number; // if (prevPG) { - // caBegin = prevPG.pos; + // caBegin = client.localReferencePositionToPosition(prevPG); // } else { // caBegin = 0; // } // curPG.pos is ca end - const curPG = client.findTile(pos, "pg", false)!; - const properties = curPG.tile.properties!; + const curPG = client.searchForMarker(pos, "pg", true)!; + const properties = curPG.properties!; const curSegOff = client.getContainingSegment(pos)!; const curSeg = curSegOff.segment!; // Combine paragraph and direct properties diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index d04d9bff95c9..48b47ef90200 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -623,11 +623,6 @@ export class SharedString extends SharedSegmentSequence imp annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): void; annotateMarkerNotifyConsensus(marker: Marker, props: PropertySet, callback: (m: Marker) => void): void; static create(runtime: IFluidDataStoreRuntime, id?: string): SharedString; - // @deprecated - findTile(startPos: number | undefined, tileLabel: string, preceding?: boolean): { - tile: ReferencePosition; - pos: number; - } | undefined; static getFactory(): SharedStringFactory; getMarkerFromId(id: string): ISegment | undefined; getText(start?: number, end?: number): string; diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 4c45941a2e57..3f2994a605bf 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -13,7 +13,6 @@ import { ISegmentAction, Marker, PropertySet, - ReferencePosition, ReferenceType, refHasTileLabel, TextSegment, @@ -217,28 +216,6 @@ export class SharedString this.guardReentrancy(() => this.client.annotateMarker(marker, props, combiningOp)); } - /** - * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`. - * Note that Markers receive `ReferenceType.Tile` by default. - * @deprecated Use `searchForMarker` instead. - * @param startPos - Position at which to start the search - * @param clientId - clientId dictating the perspective to search from - * @param tileLabel - Label of the tile to search for - * @param preceding - Whether the desired tile comes before (true) or after (false) `startPos` - */ - public findTile( - startPos: number | undefined, - tileLabel: string, - preceding = true, - ): - | { - tile: ReferencePosition; - pos: number; - } - | undefined { - return this.client.findTile(startPos ?? 0, tileLabel, preceding); - } - /** * Searches a string for the nearest marker in either direction to a given start position. * The search will include the start position, so markers at the start position are valid From a33007439b856f041127d16a92a7e8e43a21fc39 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:50:56 -0400 Subject: [PATCH 15/50] refactor(merge-tree sequence): misc cleanup (#18037) Mixes together miscellaneous changes from some of my PRs. --- .../merge-tree/api-report/merge-tree.api.md | 2 +- packages/dds/merge-tree/src/client.ts | 39 +++------ .../dds/merge-tree/src/collections/index.ts | 2 +- .../dds/merge-tree/src/collections/list.ts | 29 +++++-- .../dds/merge-tree/src/collections/rbTree.ts | 18 ++-- packages/dds/merge-tree/src/localReference.ts | 29 +++---- packages/dds/merge-tree/src/mergeTree.ts | 86 ++++++++++++------- packages/dds/merge-tree/src/mergeTreeNodes.ts | 2 +- packages/dds/merge-tree/src/partialLengths.ts | 68 +++++++++++---- packages/dds/merge-tree/src/revertibles.ts | 10 ++- .../merge-tree/src/segmentGroupCollection.ts | 6 +- .../src/test/collections.list.spec.ts | 11 +-- .../merge-tree/src/test/partialLength.spec.ts | 4 +- .../dds/merge-tree/src/test/testClient.ts | 7 +- .../dds/sequence/api-report/sequence.api.md | 2 +- .../dds/sequence/src/intervals/interval.ts | 21 ++--- .../src/intervals/sequenceInterval.ts | 19 +--- 17 files changed, 198 insertions(+), 157 deletions(-) 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 82e3496316b0..d10668dfc9d6 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -58,7 +58,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // (undocumented) hasProperty(key: string): boolean; // (undocumented) - isLeaf(): boolean; + isLeaf(): this is ISegment; // (undocumented) localRefs?: LocalReferenceCollection; // (undocumented) diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index df6a2edde967..214feeefb71c 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -17,7 +17,7 @@ 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 { @@ -182,6 +182,7 @@ export class Client extends TypedEventEmitter { return undefined; } } + /** * Annotates the markers with the provided properties * @param marker - The marker to annotate @@ -195,9 +196,10 @@ export class Client extends TypedEventEmitter { combiningOp?: ICombiningOp, ): IMergeTreeAnnotateMsg | undefined { const annotateOp = createAnnotateMarkerOp(marker, props, combiningOp)!; - - return this.applyAnnotateRangeOp({ op: annotateOp }) ? annotateOp : undefined; + this.applyAnnotateRangeOp({ op: annotateOp }); + return annotateOp; } + /** * Annotates the range with the provided properties * @param start - The inclusive start position of the range to annotate @@ -213,11 +215,8 @@ export class Client extends TypedEventEmitter { combiningOp: ICombiningOp | undefined, ): IMergeTreeAnnotateMsg | undefined { const annotateOp = createAnnotateRangeOp(start, end, props, combiningOp); - - if (this.applyAnnotateRangeOp({ op: annotateOp })) { - return annotateOp; - } - return undefined; + this.applyAnnotateRangeOp({ op: annotateOp }); + return annotateOp; } /** @@ -435,9 +434,8 @@ export class Client extends TypedEventEmitter { /** * 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!" */, @@ -455,16 +453,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!" */, @@ -473,10 +468,6 @@ 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, @@ -487,8 +478,6 @@ export class Client extends TypedEventEmitter { clientArgs.sequenceNumber, opArgs, ); - - return true; } /** @@ -505,10 +494,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)]; @@ -526,6 +511,7 @@ export class Client extends TypedEventEmitter { clientArgs.sequenceNumber, opArgs, ); + return true; } @@ -919,9 +905,10 @@ export class Client extends TypedEventEmitter { private lastNormalizationRefSeq = 0; - private pendingRebase: List | undefined; + 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 diff --git a/packages/dds/merge-tree/src/collections/index.ts b/packages/dds/merge-tree/src/collections/index.ts index 2173df71a865..200c09ebc981 100644 --- a/packages/dds/merge-tree/src/collections/index.ts +++ b/packages/dds/merge-tree/src/collections/index.ts @@ -4,7 +4,7 @@ */ export { Comparer, Heap } from "./heap"; -export { List, ListNode, ListNodeRange, walkList } from "./list"; +export { DoublyLinkedList, ListNode, ListNodeRange, walkList } from "./list"; export { ConflictAction, Dictionary, 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 a91971200eec..67ba6b5a0784 100644 --- a/packages/dds/merge-tree/src/collections/rbTree.ts +++ b/packages/dds/merge-tree/src/collections/rbTree.ts @@ -637,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); } } @@ -646,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); } } @@ -655,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); } } @@ -671,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); } } @@ -680,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); } } @@ -689,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/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index 460574dcd078..2a8e45399c68 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -5,7 +5,7 @@ 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"; @@ -150,9 +150,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 { @@ -325,9 +325,9 @@ 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); @@ -411,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; } @@ -460,7 +458,7 @@ export class LocalReferenceCollection { * @internal */ 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 }); @@ -495,7 +493,7 @@ export class LocalReferenceCollection { */ 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 }); @@ -551,7 +549,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, @@ -573,7 +572,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 a64f7856dd1a..aa22fd405a79 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -9,7 +9,7 @@ import { assert } from "@fluidframework/core-utils"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils"; import { IAttributionCollectionSerializer } from "./attributionCollection"; -import { Comparer, Heap, List, ListNode } from "./collections"; +import { Comparer, Heap, DoublyLinkedList, ListNode } from "./collections"; import { NonCollabClient, TreeMaintenanceSequenceNumber, @@ -420,7 +420,7 @@ export class MergeTree { public readonly collabWindow = new CollaborationWindow(); - public readonly pendingSegments = new List(); + public readonly pendingSegments = new DoublyLinkedList(); public readonly segmentsToScour = new Heap([], LRUSegmentComparer); public readonly attributionPolicy: AttributionPolicy | undefined; @@ -658,8 +658,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; @@ -950,13 +949,32 @@ 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); @@ -1175,9 +1193,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"); } @@ -1319,7 +1336,7 @@ export class MergeTree { 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 && @@ -1424,20 +1441,19 @@ 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; } else { return true; } @@ -1556,7 +1572,17 @@ export class MergeTree { 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; @@ -1709,7 +1735,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 && @@ -1954,7 +1980,7 @@ export class MergeTree { } // 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, @@ -2047,7 +2073,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( @@ -2076,7 +2102,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 = () => { @@ -2099,7 +2125,7 @@ export class MergeTree { currentRangeToNormalize.push(seg); } else { normalize(); - currentRangeToNormalize = new List(); + currentRangeToNormalize = new DoublyLinkedList(); rangeContainsLocalSegs = false; rangeContainsRemoteRemovedSegs = false; } @@ -2166,6 +2192,8 @@ export class MergeTree { } else { node.partialLengths = PartialSequenceLengths.combine(node, this.collabWindow); } + + PartialSequenceLengths.options.verifyExpected?.(this, node, seq, clientId); } } diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 12c4b9df7259..77ea7fa04cd1 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -420,7 +420,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { return !!this.properties && this.properties[key] !== undefined; } - public isLeaf() { + public isLeaf(): this is ISegment { return true; } diff --git a/packages/dds/merge-tree/src/partialLengths.ts b/packages/dds/merge-tree/src/partialLengths.ts index dab2a2dca1ee..dadd88677f80 100644 --- a/packages/dds/merge-tree/src/partialLengths.ts +++ b/packages/dds/merge-tree/src/partialLengths.ts @@ -10,12 +10,14 @@ import { CollaborationWindow, compareNumbers, IMergeBlock, + IMergeNode, IRemovalInfo, ISegment, seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; import { SortedSet } from "./sortedSet"; +import { MergeTree } from "./mergeTree"; class PartialSequenceLengthsSet extends SortedSet { protected getKey(item: PartialSequenceLength): number { @@ -189,6 +191,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; } @@ -854,7 +863,7 @@ function verifyPartialLengths( partialSeqLengths: PartialSequenceLengths, partialLengths: PartialSequenceLengthsSet, clientPartials: boolean, -) { +): number { if (partialLengths.size === 0) { return 0; } @@ -864,7 +873,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 @@ -911,7 +920,7 @@ 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; } @@ -919,12 +928,50 @@ function verifyPartialLengths( 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); } } @@ -933,17 +980,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( diff --git a/packages/dds/merge-tree/src/revertibles.ts b/packages/dds/merge-tree/src/revertibles.ts index 2df11e01648f..4cf1907a3b40 100644 --- a/packages/dds/merge-tree/src/revertibles.ts +++ b/packages/dds/merge-tree/src/revertibles.ts @@ -5,7 +5,7 @@ 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"; @@ -296,7 +296,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 @@ -306,10 +308,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 53240e981ac6..f19b447b03d7 100644 --- a/packages/dds/merge-tree/src/segmentGroupCollection.ts +++ b/packages/dds/merge-tree/src/segmentGroupCollection.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. */ -import { List, walkList } from "./collections"; +import { DoublyLinkedList, walkList } from "./collections"; import { ISegment, SegmentGroup } from "./mergeTreeNodes"; export class SegmentGroupCollection { - private readonly segmentGroups: List; + private readonly segmentGroups: DoublyLinkedList; constructor(private readonly segment: ISegment) { - this.segmentGroups = new List(); + this.segmentGroups = new DoublyLinkedList(); } public get size() { 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/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/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index 8c53726f4363..1408a2462038 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"; @@ -134,8 +134,9 @@ 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) { diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 48b47ef90200..c4a7c9822f5f 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -230,7 +230,7 @@ export class Interval implements ISerializableInterval { overlaps(b: Interval): boolean; properties: PropertySet; // @internal (undocumented) - propertyManager: PropertiesManager; + readonly propertyManager: PropertiesManager; // @internal (undocumented) serialize(): ISerializedInterval; // (undocumented) diff --git a/packages/dds/sequence/src/intervals/interval.ts b/packages/dds/sequence/src/intervals/interval.ts index 784a14d2d200..2ba00a5562e8 100644 --- a/packages/dds/sequence/src/intervals/interval.ts +++ b/packages/dds/sequence/src/intervals/interval.ts @@ -27,22 +27,22 @@ export class Interval implements ISerializableInterval { /** * {@inheritDoc ISerializableInterval.properties} */ - public properties: PropertySet; + public properties: PropertySet = createMap(); + /** @internal */ public auxProps: PropertySet[] | undefined; + /** * {@inheritDoc ISerializableInterval.propertyManager} * @internal */ - 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); } @@ -177,7 +177,6 @@ export class Interval implements ISerializableInterval { op?: ICombiningOp, ): PropertySet | undefined { if (newProps) { - this.initializeProperties(); return this.propertyManager.addProperties( this.properties, newProps, @@ -213,7 +212,6 @@ export class Interval implements ISerializableInterval { } const newInterval = new Interval(startPos, endPos); if (this.properties) { - newInterval.initializeProperties(); this.propertyManager.copyTo( this.properties, newInterval.properties, @@ -222,15 +220,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/sequenceInterval.ts b/packages/dds/sequence/src/intervals/sequenceInterval.ts index 085b988a71d7..b5e798b9be19 100644 --- a/packages/dds/sequence/src/intervals/sequenceInterval.ts +++ b/packages/dds/sequence/src/intervals/sequenceInterval.ts @@ -106,12 +106,13 @@ export class SequenceInterval implements ISerializableInterval { /** * {@inheritDoc ISerializableInterval.properties} */ - public properties: PropertySet; + public properties: PropertySet = createMap(); + /** * {@inheritDoc ISerializableInterval.propertyManager} * @internal */ - public propertyManager: PropertiesManager; + public propertyManager: PropertiesManager = new PropertiesManager(); /** * @internal @@ -144,9 +145,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); } @@ -342,7 +340,6 @@ export class SequenceInterval implements ISerializableInterval { seq?: number, op?: ICombiningOp, ): PropertySet | undefined { - this.initializeProperties(); return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab); } @@ -429,7 +426,6 @@ export class SequenceInterval implements ISerializableInterval { endSide ?? this.endSide, ); if (this.properties) { - newInterval.initializeProperties(); this.propertyManager.copyTo( this.properties, newInterval.properties, @@ -438,15 +434,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( From cb87403ccd1ad0b60e0e3f547e79a2bb01d05dfe Mon Sep 17 00:00:00 2001 From: msfluid-bot <69654365+msfluid-bot@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:37:05 -0700 Subject: [PATCH 16/50] Automation: main-next integrate (#18041) ## main-next integrate PR ***DO NOT MERGE THIS PR USING THE GITHUB UI.*** The aim of this pull request is to sync main and next branch. This branch has **MERGE CONFLICTS** with next due to this commit. If this PR is assigned to you, you need to do the following: 1. Acknowledge the pull request by adding a comment -- "Actively working on it". 2. Merge main-next-ced259c into the target branch, next. **The direction of the merge matters!** You need to checkout next and merge main-next-ced259c into it, then fast-forward main-next-ced259c to the merge commit. To do that use the following git commands: - `git fetch --all` -- this ensures your remote refs are updated - `git remote -v` -- displays the list of remote repositories associated with your Git repository along with their corresponding URLs. You have to choose the remote associated with the **microsoft/FluidFramework** repository. Change the remote name in these example commands if yours is not upstream. - `git checkout -b next-merge-head upstream/next` -- make a temporary branch at next. - `git merge upstream/main-next-ced259c` -- merge next into main-next-ced259c 3. Resolve any merge conflicts between the branches, then commit all the changes using the following commands: - `git add .` -- stage all the local changes - `git commit -m "Automation: main-next integrate"` -- commit the merge 4. Fast-forward the main-next-ced259c branch to the merge commit and push to the remote. - `git checkout main-next-ced259c` -- check out the mergeBranch locally - `git merge next-merge-head --ff-only` -- fast-forward to the merge commit - `git push upstream` -- and push it to upstream (your upstream remote name may be different) 5. Address any CI failures. If you need to make additional changes to the PR, **always amend the commit** using the following git commands: - `git commit --amend -m "Automation: main-next integrate"` - `git push --force-with-lease` Once CI passes and the PR is ready to merge, add the "msftbot: merge-next" label to the PR and one of the people with merge permissions will merge it in. Co-authored-by: Daniel Madrid <105010181+dannimad@users.noreply.github.com> Co-authored-by: danielcar --- .../api-report/container-definitions.api.md | 11 +++++-- .../common/container-definitions/src/index.ts | 1 + .../container-definitions/src/runtime.ts | 25 +++++++++++++-- .../api-report/container-loader.api.md | 4 +-- .../loader/container-loader/src/container.ts | 20 ++++++++---- .../api-report/container-runtime.api.md | 5 ++- .../container-runtime/src/blobManager.ts | 32 +++++++++++++++---- .../container-runtime/src/containerRuntime.ts | 8 ++--- .../src/test/stashedOps.spec.ts | 24 ++++++++++++++ 9 files changed, 102 insertions(+), 28 deletions(-) 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 048b6458926b..a2b4dc2a29fd 100644 --- a/packages/common/container-definitions/api-report/container-definitions.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.api.md @@ -383,6 +383,12 @@ export interface IFluidPackageEnvironment { export { IGenericError } +// @alpha +export interface IGetPendingLocalStateProps { + readonly notifyImminentClosure: boolean; + readonly stopBlobAttachingSignal?: AbortSignal; +} + // @public export interface IHostLoader extends ILoader { createDetachedContainer(codeDetails: IFluidCodeDetails, createDetachedProps?: { @@ -456,9 +462,8 @@ export interface IResolvedFluidCodeDetails extends IFluidCodeDetails { export interface IRuntime extends IDisposable { createSummary(blobRedirectTable?: Map): ISummaryTree; getEntryPoint(): Promise; - getPendingLocalState(props?: { - notifyImminentClosure?: boolean; - }): unknown; + // @alpha + getPendingLocalState(props?: IGetPendingLocalStateProps): unknown; notifyOpReplay?(message: ISequencedDocumentMessage): Promise; process(message: ISequencedDocumentMessage, local: boolean): any; processSignal(message: any, local: boolean): any; diff --git a/packages/common/container-definitions/src/index.ts b/packages/common/container-definitions/src/index.ts index 1d56cb6f7a30..3fab1cba931d 100644 --- a/packages/common/container-definitions/src/index.ts +++ b/packages/common/container-definitions/src/index.ts @@ -65,6 +65,7 @@ export { IProvideRuntimeFactory, IRuntime, IRuntimeFactory, + IGetPendingLocalStateProps, } from "./runtime"; export { diff --git a/packages/common/container-definitions/src/runtime.ts b/packages/common/container-definitions/src/runtime.ts index 8af25d6a242e..fbdf3d5be310 100644 --- a/packages/common/container-definitions/src/runtime.ts +++ b/packages/common/container-definitions/src/runtime.ts @@ -99,10 +99,10 @@ export interface IRuntime extends IDisposable { /** * Get pending local state in a serializable format to be given back to a newly loaded container - * @experimental + * @alpha * {@link https://github.com/microsoft/FluidFramework/packages/tree/main/loader/container-loader/closeAndGetPendingLocalState.md} */ - getPendingLocalState(props?: { notifyImminentClosure?: boolean }): unknown; + getPendingLocalState(props?: IGetPendingLocalStateProps): unknown; /** * Notify runtime that we have processed a saved message, so that it can do async work (applying @@ -243,3 +243,24 @@ export interface IRuntimeFactory extends IProvideRuntimeFactory { */ instantiateRuntime(context: IContainerContext, existing: boolean): Promise; } + +/** + * Defines list of properties expected for getPendingLocalState + * @alpha + */ +export interface IGetPendingLocalStateProps { + /** + * Indicates the container will close after getting the pending state. Used internally + * to wait for blobs to be attached to a DDS and collect generated ops before closing. + */ + readonly notifyImminentClosure: boolean; + + /** + * Abort signal to stop waiting for blobs to get attached to a DDS. When triggered, + * only blobs attached will be collected in the pending state. + * Intended to be used in the very rare scenario in which getLocalPendingState go stale due + * to a blob failed to be referenced. Such a blob will be lost but the rest of the state will + * be preserved and collected. + */ + readonly stopBlobAttachingSignal?: AbortSignal; +} 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 65f938872393..93dd9e08d07f 100644 --- a/packages/loader/container-loader/api-report/container-loader.api.md +++ b/packages/loader/container-loader/api-report/container-loader.api.md @@ -42,9 +42,9 @@ export interface ICodeDetailsLoader extends Partial; } -// @public +// @alpha export interface IContainerExperimental extends IContainer { - closeAndGetPendingLocalState?(): Promise; + closeAndGetPendingLocalState?(stopBlobAttachingSignal?: AbortSignal): Promise; getPendingLocalState?(): Promise; } diff --git a/packages/loader/container-loader/src/container.ts b/packages/loader/container-loader/src/container.ts index 4acc223edd48..a8d773c098cb 100644 --- a/packages/loader/container-loader/src/container.ts +++ b/packages/loader/container-loader/src/container.ts @@ -40,6 +40,7 @@ import { IRuntime, ReadOnlyInfo, isFluidCodeDetails, + IGetPendingLocalStateProps, } from "@fluidframework/container-definitions"; import { IDocumentService, @@ -1112,12 +1113,17 @@ export class Container } } - public async closeAndGetPendingLocalState(): Promise { + public async closeAndGetPendingLocalState( + stopBlobAttachingSignal?: AbortSignal, + ): Promise { // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the // container at the same time we get pending state, otherwise this container could reconnect and resubmit with // a new clientId and a future container using stale pending state without the new clientId would resubmit them this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127 - const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true }); + const pendingState = await this.getPendingLocalStateCore({ + notifyImminentClosure: true, + stopBlobAttachingSignal, + }); this.close(); return pendingState; } @@ -1126,7 +1132,7 @@ export class Container return this.getPendingLocalStateCore({ notifyImminentClosure: false }); } - private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) { + private async getPendingLocalStateCore(props: IGetPendingLocalStateProps) { return PerformanceEvent.timedExecAsync( this.mc.logger, { @@ -2517,7 +2523,7 @@ export class Container /** * IContainer interface that includes experimental features still under development. - * @experimental + * @alpha */ export interface IContainerExperimental extends IContainer { /** @@ -2525,7 +2531,7 @@ export interface IContainerExperimental extends IContainer { * submission and potential document corruption. The blob returned MUST be deleted if and when this * container emits a "connected" event. * @returns serialized blob that can be passed to Loader.resolve() - * @experimental misuse of this API can result in duplicate op submission and potential document corruption + * @alpha misuse of this API can result in duplicate op submission and potential document corruption * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md} */ getPendingLocalState?(): Promise; @@ -2533,8 +2539,8 @@ export interface IContainerExperimental extends IContainer { /** * Closes the container and returns serialized local state intended to be * given to a newly loaded container. - * @experimental + * @alpha * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md} */ - closeAndGetPendingLocalState?(): Promise; + closeAndGetPendingLocalState?(stopBlobAttachingSignal?: AbortSignal): Promise; } 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 5a8507a8d2b3..c690d5600107 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -28,6 +28,7 @@ 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'; @@ -155,9 +156,7 @@ export class ContainerRuntime extends TypedEventEmitter; getNodeType(nodePath: string): GCNodeType; // (undocumented) - getPendingLocalState(props?: { - notifyImminentClosure: boolean; - }): Promise; + getPendingLocalState(props?: IGetPendingLocalStateProps): Promise; // (undocumented) getQuorum(): IQuorumClients; // @deprecated diff --git a/packages/runtime/container-runtime/src/blobManager.ts b/packages/runtime/container-runtime/src/blobManager.ts index 146661a639df..ca0ffaeff1fd 100644 --- a/packages/runtime/container-runtime/src/blobManager.ts +++ b/packages/runtime/container-runtime/src/blobManager.ts @@ -167,6 +167,7 @@ export class BlobManager extends TypedEventEmitter { private readonly tombstonedBlobs: Set = new Set(); private readonly sendBlobAttachOp: (localId: string, storageId?: string) => void; + private stopAttaching: boolean = false; constructor( private readonly routeContext: IFluidHandleContext, @@ -518,7 +519,7 @@ export class BlobManager extends TypedEventEmitter { private onUploadResolve(localId: string, response: ICreateBlobResponseWithTTL) { const entry = this.pendingBlobs.get(localId); assert(entry !== undefined, 0x6c8 /* pending blob entry not found for uploaded blob */); - if (entry.abortSignal?.aborted === true && !entry.opsent) { + if ((entry.abortSignal?.aborted === true && !entry.opsent) || this.stopAttaching) { this.deletePendingBlob(localId); return; } @@ -899,7 +900,9 @@ export class BlobManager extends TypedEventEmitter { } } - public async attachAndGetPendingBlobs(): Promise { + public async attachAndGetPendingBlobs( + stopBlobAttachingSignal?: AbortSignal, + ): Promise { return PerformanceEvent.timedExecAsync( this.mc.logger, { eventName: "GetPendingBlobs" }, @@ -914,12 +917,17 @@ export class BlobManager extends TypedEventEmitter { for (const [id, entry] of this.pendingBlobs) { if (!localBlobs.has(entry)) { localBlobs.add(entry); - if (!entry.opsent) { - this.sendBlobAttachOp(id, entry.storageId); - } entry.handleP.resolve(this.getBlobHandle(id)); attachBlobsP.push( - new Promise((resolve) => { + new Promise((resolve, reject) => { + stopBlobAttachingSignal?.addEventListener( + "abort", + () => { + this.stopAttaching = true; + reject(new Error("Operation aborted")); + }, + { once: true }, + ); const onBlobAttached = (attachedEntry) => { if (attachedEntry === entry) { this.off("blobAttached", onBlobAttached); @@ -935,11 +943,21 @@ export class BlobManager extends TypedEventEmitter { ); } } - await Promise.all(attachBlobsP); + await Promise.allSettled(attachBlobsP).catch(() => {}); } for (const [id, entry] of this.pendingBlobs) { + if (stopBlobAttachingSignal?.aborted && !entry.attached) { + this.mc.logger.sendTelemetryEvent({ + eventName: "UnableToStashBlob", + id, + }); + continue; + } assert(entry.attached === true, 0x790 /* stashed blob should be attached */); + if (!entry.opsent) { + this.sendBlobAttachOp(id, entry.storageId); + } blobs[id] = { blob: bufferToString(entry.blob, "base64"), storageId: entry.storageId, diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 555a7b56d993..5d5c124dcc30 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -25,6 +25,7 @@ import { ILoaderOptions, ILoader, LoaderHeader, + IGetPendingLocalStateProps, } from "@fluidframework/container-definitions"; import { IContainerRuntime, @@ -3930,9 +3931,7 @@ export class ContainerRuntime ); } - public async getPendingLocalState(props?: { - notifyImminentClosure: boolean; - }): Promise { + public async getPendingLocalState(props?: IGetPendingLocalStateProps): Promise { return PerformanceEvent.timedExecAsync( this.mc.logger, { @@ -3942,6 +3941,7 @@ export class ContainerRuntime async (event) => { this.verifyNotClosed(); const waitBlobsToAttach = props?.notifyImminentClosure; + const stopBlobAttachingSignal = props?.stopBlobAttachingSignal; if (this._orderSequentiallyCalls !== 0) { throw new UsageError("can't get state during orderSequentially"); } @@ -3950,7 +3950,7 @@ export class ContainerRuntime // to close current batch. this.flush(); const pendingAttachmentBlobs = waitBlobsToAttach - ? await this.blobManager.attachAndGetPendingBlobs() + ? await this.blobManager.attachAndGetPendingBlobs(stopBlobAttachingSignal) : undefined; const pending = this.pendingStateManager.getLocalState(); if (!pendingAttachmentBlobs && !this.hasPendingMessages()) { diff --git a/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts b/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts index 59e5a15503ab..e20465e5b19d 100644 --- a/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts +++ b/packages/test/test-end-to-end-tests/src/test/stashedOps.spec.ts @@ -1261,6 +1261,30 @@ describeNoCompat("stashed ops", (getTestObjectProvider) => { ); }); + it("abort while stashing blobs", async function () { + const dataStore = await requestFluidObject(container1, "default"); + const map = await dataStore.getSharedObject(mapId); + const ac = new AbortController(); + await provider.ensureSynchronized(); + + const blobP1 = dataStore.runtime.uploadBlob(stringToBuffer("blob contents", "utf8")); + const blobP2 = dataStore.runtime.uploadBlob(stringToBuffer("blob contents", "utf8")); + assert(container1.closeAndGetPendingLocalState); + const pendingOpsP = container1.closeAndGetPendingLocalState(ac.signal); + map.set("blob handle", await blobP1); + ac.abort(); + const pendingOps = await pendingOpsP; + + const container2 = await loader.resolve({ url }, pendingOps); + const dataStore2 = await requestFluidObject(container2, "default"); + const map2 = await dataStore2.getSharedObject(mapId); + await provider.ensureSynchronized(); + assert.strictEqual( + bufferToString(await map2.get("blob handle").get(), "utf8"), + "blob contents", + ); + }); + it("close while uploading multiple blob", async function () { const dataStore = await requestFluidObject(container1, "default"); const map = await dataStore.getSharedObject(mapId); From 037c64421e305275bd75ec11e7933c9ef0fe67a1 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Tue, 31 Oct 2023 20:15:42 -0400 Subject: [PATCH 17/50] add skipped obliterate tests (#18056) Splits out the obliterate tests into their own PR. Skipped for now. I'm also excited about the improvements to the reconnect test helper. --- .../src/test/obliterate.concurrent.spec.ts | 1824 +++++++++++++++++ .../src/test/obliterate.partialLength.spec.ts | 90 +- .../src/test/obliterate.reconnect.spec.ts | 315 +-- .../merge-tree/src/test/obliterate.spec.ts | 71 +- .../merge-tree/src/test/reconnectHelper.ts | 114 ++ 5 files changed, 2234 insertions(+), 180 deletions(-) create mode 100644 packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts create mode 100644 packages/dds/merge-tree/src/test/reconnectHelper.ts 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..998143338ac3 --- /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.skip(`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..9b74109c48c4 100644 --- a/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts @@ -5,11 +5,11 @@ 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"; -describe("obliterate partial lengths", () => { +describe.skip("obliterate partial lengths", () => { let client: TestClient; let refSeq: number; const localClientId = 17; @@ -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..fc776262e75f 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.skip(`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..6a8a52e1fdb1 100644 --- a/packages/dds/merge-tree/src/test/obliterate.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.spec.ts @@ -4,7 +4,6 @@ */ import { strict as assert } from "assert"; -import { UnassignedSequenceNumber } from "../constants"; import { MergeTreeDeltaType } from "../ops"; import { TestClient } from "./testClient"; import { insertText } from "./testUtils"; @@ -13,10 +12,12 @@ describe.skip("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/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), + ); + } +} From 295ee26367c841ab1159938a6389f43a60188f4e Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Wed, 1 Nov 2023 13:07:19 -0700 Subject: [PATCH 18/50] feat(merge-tree): obliterate types (#18100) Adds some of the types and fields related to obliterate. --- .../merge-tree/api-report/merge-tree.api.md | 19 ++++- packages/dds/merge-tree/package.json | 4 + packages/dds/merge-tree/src/client.ts | 57 ++++++++++++- packages/dds/merge-tree/src/mergeTree.ts | 17 ++++ .../merge-tree/src/mergeTreeDeltaCallback.ts | 3 +- packages/dds/merge-tree/src/mergeTreeNodes.ts | 81 ++++++++++++++++++- packages/dds/merge-tree/src/opBuilder.ts | 17 ++++ packages/dds/merge-tree/src/ops.ts | 23 +++++- .../dds/merge-tree/src/test/testClient.ts | 6 +- .../validateMergeTreePrevious.generated.ts | 2 + 10 files changed, 217 insertions(+), 12 deletions(-) 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 d10668dfc9d6..ba1cd91a1fdf 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -60,12 +60,20 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // (undocumented) isLeaf(): this is ISegment; // (undocumented) + localMovedSeq?: number; + // (undocumented) localRefs?: LocalReferenceCollection; // (undocumented) localRemovedSeq?: number; // (undocumented) localSeq?: number; // (undocumented) + movedClientIds?: number[]; + // (undocumented) + movedSeq?: number; + // (undocumented) + movedSeqs?: number[]; + // (undocumented) properties?: PropertySet; // (undocumented) propertyManager?: PropertiesManager; @@ -85,6 +93,8 @@ export abstract class BaseSegment extends MergeNode implements ISegment { readonly trackingCollection: TrackingGroupCollection; // (undocumented) abstract readonly type: string; + // (undocumented) + wasMovedOnInsert?: boolean | undefined; } // @public (undocumented) @@ -153,6 +163,8 @@ export class Client extends TypedEventEmitter { readonly logger: ITelemetryLoggerExt; // (undocumented) longClientId: string | undefined; + // @alpha + obliterateRangeLocal(start: number, end: number): IMergeTreeObliterateMsg; peekPendingSegmentGroups(count?: number): SegmentGroup | SegmentGroup[] | undefined; posFromRelativePos(relativePos: IRelativePosition): number; regeneratePendingOp(resetOp: IMergeTreeOp, segmentGroup: SegmentGroup | SegmentGroup[]): IMergeTreeOp; @@ -429,7 +441,7 @@ export interface IMergeTreeDeltaCallbackArgs { +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; @@ -716,7 +728,7 @@ export class MergeNode implements IMergeNodeCommon { export type MergeTreeDeltaCallback = (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs) => void; // @public (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; // @public (undocumented) export type MergeTreeDeltaOperationTypes = MergeTreeDeltaOperationType | MergeTreeMaintenanceType; @@ -740,6 +752,7 @@ export const MergeTreeDeltaType: { readonly REMOVE: 1; readonly ANNOTATE: 2; readonly GROUP: 3; + readonly OBLITERATE: 4; }; // @public (undocumented) diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index 96cf49ffc505..bcdc3b8dff08 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -126,6 +126,7 @@ "backCompat": false }, "ClassDeclaration_Client": { + "forwardCompat": false, "backCompat": false }, "RemovedTypeAliasDeclaration_RangeStackMap": { @@ -170,6 +171,9 @@ }, "ClassDeclaration_TextSegment": { "backCompat": false + }, + "VariableDeclaration_MergeTreeDeltaType": { + "forwardCompat": false } } } diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index 214feeefb71c..daf69580bbc2 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -40,6 +40,7 @@ import { // eslint-disable-next-line import/no-deprecated createGroupOp, createInsertSegmentOp, + createObliterateRangeOp, createRemoveRangeOp, } from "./opBuilder"; import { @@ -55,6 +56,7 @@ import { IRelativePosition, MergeTreeDeltaType, ReferenceType, + IMergeTreeObliterateMsg, } from "./ops"; import { PropertySet } from "./properties"; import { SnapshotLegacy } from "./snapshotlegacy"; @@ -231,6 +233,21 @@ 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 + * + * @alpha + */ + 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 @@ -431,6 +448,26 @@ export class Client extends TypedEventEmitter { 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 @@ -521,7 +558,11 @@ export class Client extends TypedEventEmitter { * @param clientArgs - The client args for the op */ private getValidOpRange( - op: IMergeTreeAnnotateMsg | IMergeTreeInsertMsg | IMergeTreeRemoveMsg, + op: + | IMergeTreeAnnotateMsg + | IMergeTreeInsertMsg + | IMergeTreeRemoveMsg + | IMergeTreeObliterateMsg, clientArgs: IMergeTreeClientSequenceArgs, ): IIntegerRange { let start: number | undefined = op.pos1; @@ -566,6 +607,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, @@ -807,6 +852,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({ @@ -842,6 +890,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: @@ -1058,6 +1110,9 @@ export class Client extends TypedEventEmitter { case MergeTreeDeltaType.REMOVE: this.applyRemoveRangeOp(opArgs); break; + case MergeTreeDeltaType.OBLITERATE: + this.applyObliterateRangeOp(opArgs); + break; default: break; } diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index aa22fd405a79..b771a3a51ca8 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -1691,6 +1691,23 @@ export class MergeTree { } } + /** + * @alpha + */ + public obliterateRange( + start: number, + end: number, + refSeq: number, + clientId: number, + seq: number, + overwrite: boolean = false, + opArgs: IMergeTreeDeltaOpArgs, + ): void { + throw new UsageError( + "Attempted to use obliterate. Obliterate is not currently implemented.", + ); + } + public markRangeRemoved( start: number, end: number, diff --git a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts index 4da8cf26997a..03dd820553a7 100644 --- a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts +++ b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts @@ -11,7 +11,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. diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 77ea7fa04cd1..2a0ec65772eb 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -114,11 +114,76 @@ 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. + */ +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; +} + /** * A segment representing a portion of the merge tree. * Segments are leaf nodes of the merge tree and contain data. */ -export interface ISegment extends IMergeNodeCommon, Partial { +export interface ISegment extends IMergeNodeCommon, Partial, Partial { readonly type: string; readonly segmentGroups: SegmentGroupCollection; readonly trackingCollection: TrackingGroupCollection; @@ -384,6 +449,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); /** @@ -396,6 +465,7 @@ 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, @@ -431,6 +501,10 @@ export abstract class BaseSegment extends MergeNode implements ISegment { 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(); } @@ -509,6 +583,11 @@ export abstract class BaseSegment extends MergeNode implements ISegment { 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) { diff --git a/packages/dds/merge-tree/src/opBuilder.ts b/packages/dds/merge-tree/src/opBuilder.ts index 685098203896..f32cb3053886 100644 --- a/packages/dds/merge-tree/src/opBuilder.ts +++ b/packages/dds/merge-tree/src/opBuilder.ts @@ -13,6 +13,7 @@ import { IMergeTreeRemoveMsg, MergeTreeDeltaType, IMergeTreeDeltaOp, + IMergeTreeObliterateMsg, } from "./ops"; import { PropertySet } from "./properties"; @@ -79,6 +80,22 @@ 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 + */ +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 diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index 5bd9a3cef237..dba34835e784 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -46,6 +46,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; export type MergeTreeDeltaType = (typeof MergeTreeDeltaType)[keyof typeof MergeTreeDeltaType]; @@ -94,6 +95,22 @@ export interface IMergeTreeRemoveMsg extends IMergeTreeDelta { relativePos2?: IRelativePosition; } +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; +} + export interface ICombiningOp { name: string; defaultValue?: any; @@ -123,6 +140,10 @@ export interface IJSONSegment { props?: Record; } -export type IMergeTreeDeltaOp = IMergeTreeInsertMsg | IMergeTreeRemoveMsg | IMergeTreeAnnotateMsg; +export type IMergeTreeDeltaOp = + | IMergeTreeInsertMsg + | IMergeTreeRemoveMsg + | IMergeTreeAnnotateMsg + | IMergeTreeObliterateMsg; export type IMergeTreeOp = IMergeTreeDeltaOp | IMergeTreeGroupMsg; diff --git a/packages/dds/merge-tree/src/test/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index 1408a2462038..9007a2f9d82c 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -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 { 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 44832a39c7fd..670e0391e9e4 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -80,6 +80,7 @@ declare function get_old_ClassDeclaration_Client(): declare function use_current_ClassDeclaration_Client( use: TypeOnly); use_current_ClassDeclaration_Client( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_Client()); /* @@ -1296,6 +1297,7 @@ declare function get_old_VariableDeclaration_MergeTreeDeltaType(): declare function use_current_VariableDeclaration_MergeTreeDeltaType( use: TypeOnly); use_current_VariableDeclaration_MergeTreeDeltaType( + // @ts-expect-error compatibility expected to be broken get_old_VariableDeclaration_MergeTreeDeltaType()); /* From aa12247415e474e5d7c3d51dfc4a5d3d96c9ac43 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:48:03 -0700 Subject: [PATCH 19/50] chore(merge-tree sequence): remove more public exports (#17790) Removes from the public exports or marks `@internal` a number of types and functions from merge-tree and sequence. --- .changeset/green-parrots-poke.md | 12 ++ .../prosemirror/src/fluidBridge.ts | 1 - packages/dds/matrix/src/matrix.ts | 2 - packages/dds/matrix/src/permutationvector.ts | 2 - .../merge-tree/api-report/merge-tree.api.md | 113 +++------- packages/dds/merge-tree/package.json | 62 ++++++ .../dds/merge-tree/src/MergeTreeTextHelper.ts | 2 - .../dds/merge-tree/src/attributionPolicy.ts | 2 - packages/dds/merge-tree/src/client.ts | 35 ++-- packages/dds/merge-tree/src/constants.ts | 21 +- .../dds/merge-tree/src/endOfTreeSegment.ts | 2 - packages/dds/merge-tree/src/index.ts | 25 +-- packages/dds/merge-tree/src/mergeTree.ts | 7 +- .../merge-tree/src/mergeTreeDeltaCallback.ts | 6 - packages/dds/merge-tree/src/mergeTreeNodes.ts | 39 +--- .../dds/merge-tree/src/mergeTreeTracking.ts | 2 - packages/dds/merge-tree/src/opBuilder.ts | 20 +- packages/dds/merge-tree/src/partialLengths.ts | 2 - packages/dds/merge-tree/src/properties.ts | 3 - packages/dds/merge-tree/src/revertibles.ts | 2 - .../merge-tree/src/segmentGroupCollection.ts | 8 +- .../src/segmentPropertiesManager.ts | 1 - packages/dds/merge-tree/src/snapshotLoader.ts | 1 - packages/dds/merge-tree/src/snapshotV1.ts | 2 - packages/dds/merge-tree/src/snapshotlegacy.ts | 1 - .../dds/merge-tree/src/sortedSegmentSet.ts | 6 +- packages/dds/merge-tree/src/sortedSet.ts | 2 +- packages/dds/merge-tree/src/test/beastTest.ts | 13 +- packages/dds/merge-tree/src/test/index.ts | 18 -- .../validateMergeTreePrevious.generated.ts | 194 +++--------------- packages/dds/merge-tree/src/textSegment.ts | 20 +- packages/dds/merge-tree/src/zamboni.ts | 1 - .../dds/sequence/api-report/sequence.api.md | 3 - packages/dds/sequence/package.json | 7 +- packages/dds/sequence/src/revertibles.ts | 1 - packages/dds/sequence/src/sequence.ts | 9 +- .../dds/sequence/src/sequenceDeltaEvent.ts | 2 - .../validateSequencePrevious.generated.ts | 1 + 38 files changed, 205 insertions(+), 445 deletions(-) create mode 100644 .changeset/green-parrots-poke.md 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/examples/data-objects/prosemirror/src/fluidBridge.ts b/examples/data-objects/prosemirror/src/fluidBridge.ts index 8f347ab53cc0..299df457f527 100644 --- a/examples/data-objects/prosemirror/src/fluidBridge.ts +++ b/examples/data-objects/prosemirror/src/fluidBridge.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { assert } from "@fluidframework/core-utils"; diff --git a/packages/dds/matrix/src/matrix.ts b/packages/dds/matrix/src/matrix.ts index a42f90fad13d..b63dc44c51fa 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 { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { diff --git a/packages/dds/matrix/src/permutationvector.ts b/packages/dds/matrix/src/permutationvector.ts index c6ce55069fca..9ea75d4f171b 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 { 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 b057629ff9e8..64db0c44659a 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -35,10 +35,10 @@ export interface AttributionPolicy { // @public (undocumented) export abstract class BaseSegment extends MergeNode implements ISegment { - // @deprecated (undocumented) + // @internal (undocumented) ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; // (undocumented) protected addSerializedProps(jseg: IJSONSegment): void; // (undocumented) @@ -97,7 +97,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { wasMovedOnInsert?: boolean | undefined; } -// @public @deprecated (undocumented) +// @internal (undocumented) export class Client extends TypedEventEmitter { constructor(specToSegment: (spec: IJSONSegment) => ISegment, logger: ITelemetryLoggerExt, options?: PropertySet); // (undocumented) @@ -113,8 +113,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; @@ -147,7 +145,7 @@ export class Client extends TypedEventEmitter { posAfterEnd: number | undefined; }; // (undocumented) - getShortClientId(longClientId: string): number; + protected getShortClientId(longClientId: string): number; // (undocumented) insertAtReferencePositionLocal(refPos: ReferencePosition, segment: ISegment): IMergeTreeInsertMsg | undefined; // (undocumented) @@ -181,12 +179,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; @@ -194,7 +188,7 @@ export class Client extends TypedEventEmitter { walkSegments(handler: ISegmentAction, start?: number, end?: number, accum?: undefined, splitRange?: boolean): void; } -// @public @deprecated (undocumented) +// @internal (undocumented) export class CollaborationWindow { // (undocumented) clientId: number; @@ -210,43 +204,34 @@ export class CollaborationWindow { minSeq: number; } -// @public @deprecated (undocumented) -export const compareNumbers: (a: number, b: number) => number; - // @public (undocumented) export function compareReferencePositions(a: ReferencePosition, b: ReferencePosition): number; -// @public @deprecated (undocumented) -export const compareStrings: (a: string, b: string) => number; - // @internal (undocumented) export type ConflictAction = (key: TKey, currentKey: TKey, data: TData, currentData: TData) => QProperty; -// @public @deprecated -export function createAnnotateMarkerOp(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): IMergeTreeAnnotateMsg | undefined; - -// @public @deprecated +// @internal export function createAnnotateRangeOp(start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined): IMergeTreeAnnotateMsg; // @public (undocumented) export function createDetachedLocalReferencePosition(refType?: ReferenceType): LocalReferencePosition; -// @public @deprecated (undocumented) +// @internal @deprecated (undocumented) export function createGroupOp(...ops: IMergeTreeDeltaOp[]): IMergeTreeGroupMsg; // @alpha (undocumented) export function createInsertOnlyAttributionPolicy(): AttributionPolicy; -// @public @deprecated (undocumented) +// @internal (undocumented) export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg; -// @public @deprecated (undocumented) +// @internal (undocumented) export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTreeInsertMsg; // @internal (undocumented) export function createMap(): MapLike; -// @public @deprecated +// @internal export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemoveMsg; // @public (undocumented) @@ -337,22 +322,6 @@ export interface ICombiningOp { name: string; } -// @public (undocumented) -export interface IConsensusInfo { - // (undocumented) - callback: (m: Marker) => void; - // (undocumented) - marker: Marker; -} - -// @public @deprecated (undocumented) -export interface IConsensusValue { - // (undocumented) - seq: number; - // (undocumented) - value: any; -} - // @public (undocumented) export interface IJSONMarkerSegment extends IJSONSegment { // (undocumented) @@ -377,12 +346,6 @@ export interface IMarkerDef { refType?: ReferenceType; } -// @public @deprecated (undocumented) -export interface IMarkerModifiedAction { - // (undocumented) - (marker: Marker): void; -} - // @public export interface IMergeNodeCommon { index: number; @@ -516,7 +479,7 @@ export interface IMergeTreeSegmentDelta { segment: ISegment; } -// @public @deprecated (undocumented) +// @internal (undocumented) export interface IMergeTreeTextHelper { // (undocumented) getText(refSeq: number, clientId: number, placeholder: string, start?: number, end?: number): string; @@ -552,10 +515,10 @@ export interface IRemovalInfo { // @public export interface ISegment extends IMergeNodeCommon, Partial, Partial { - // @deprecated + // @internal ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collabWindow?: CollaborationWindow, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; // (undocumented) append(segment: ISegment): void; // @alpha @@ -614,9 +577,6 @@ export interface KeyComparer { (a: TKey, b: TKey): number; } -// @public @deprecated (undocumented) -export const LocalClientId = -1; - // @public export class LocalReferenceCollection { // @internal (undocumented) @@ -725,9 +685,6 @@ export class MergeNode implements IMergeNodeCommon { ordinal: string; } -// @public @deprecated (undocumented) -export type MergeTreeDeltaCallback = (opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs) => void; - // @public (undocumented) export type MergeTreeDeltaOperationType = typeof MergeTreeDeltaType.ANNOTATE | typeof MergeTreeDeltaType.INSERT | typeof MergeTreeDeltaType.REMOVE | typeof MergeTreeDeltaType.OBLITERATE; @@ -759,9 +716,6 @@ export const MergeTreeDeltaType: { // @public (undocumented) export type MergeTreeDeltaType = (typeof MergeTreeDeltaType)[keyof typeof MergeTreeDeltaType]; -// @public @deprecated (undocumented) -export type MergeTreeMaintenanceCallback = (MaintenanceArgs: IMergeTreeMaintenanceCallbackArgs, opArgs: IMergeTreeDeltaOpArgs | undefined) => void; - // @public export const MergeTreeMaintenanceType: { readonly APPEND: -1; @@ -786,9 +740,6 @@ export interface MergeTreeRevertibleDriver { // @public (undocumented) export function minReferencePosition(a: T, b: T): T; -// @public @deprecated (undocumented) -export const NonCollabClient = -2; - // @public (undocumented) export class PropertiesManager { constructor(); @@ -969,16 +920,10 @@ export const reservedTileLabelsKey = "referenceTileLabels"; // @alpha (undocumented) export function revertMergeTreeDeltaRevertibles(driver: MergeTreeRevertibleDriver, revertibles: MergeTreeDeltaRevertible[]): void; -// @public @deprecated (undocumented) -export interface SegmentAccumulator { - // (undocumented) - segments: ISegment[]; -} - -// @public @deprecated (undocumented) +// @internal (undocumented) export interface SegmentGroup { // (undocumented) - localSeq: number; + localSeq?: number; // (undocumented) previousProps?: PropertySet[]; // (undocumented) @@ -992,13 +937,13 @@ export class SegmentGroupCollection { constructor(segment: ISegment); // (undocumented) copyTo(segment: ISegment): void; - // @deprecated (undocumented) + // @internal (undocumented) dequeue(): SegmentGroup | undefined; // (undocumented) get empty(): boolean; - // @deprecated (undocumented) + // @internal (undocumented) enqueue(segmentGroup: SegmentGroup): void; - // @deprecated (undocumented) + // @internal (undocumented) pop?(): SegmentGroup | undefined; // (undocumented) get size(): number; @@ -1040,7 +985,7 @@ export interface SortedDictionary extends Dictionary { min(): Property | undefined; } -// @public @deprecated +// @internal export class SortedSegmentSet extends SortedSet { // (undocumented) protected findItemPosition(item: T): { @@ -1051,12 +996,12 @@ export class SortedSegmentSet extends protected getKey(item: T): string; } -// @public @deprecated (undocumented) +// @internal (undocumented) export type SortedSegmentSetItem = ISegment | LocalReferencePosition | { readonly segment: ISegment; }; -// @public @deprecated (undocumented) +// @internal (undocumented) export abstract class SortedSet { // (undocumented) addOrUpdate(newItem: T, update?: (existingItem: T, newItem: T) => void): void; @@ -1097,14 +1042,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) @@ -1113,7 +1053,7 @@ export class TextSegment extends BaseSegment { readonly type = "TextSegment"; } -// @public @deprecated (undocumented) +// @internal (undocumented) export function toRemovalInfo(maybe: Partial | undefined): IRemovalInfo | undefined; // @public (undocumented) @@ -1151,13 +1091,10 @@ export class TrackingGroupCollection { unlink(trackingGroup: ITrackingGroup): boolean; } -// @public @deprecated (undocumented) -export const TreeMaintenanceSequenceNumber = -2; - -// @public @deprecated (undocumented) +// @internal export const UnassignedSequenceNumber = -1; -// @public @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 bcdc3b8dff08..02961765aa28 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -157,6 +157,64 @@ "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 }, @@ -172,6 +230,10 @@ "ClassDeclaration_TextSegment": { "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..159cb4c31fd5 100644 --- a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts +++ b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { IIntegerRange } from "./base"; import { ISegment } from "./mergeTreeNodes"; import { MergeTree } from "./mergeTree"; diff --git a/packages/dds/merge-tree/src/attributionPolicy.ts b/packages/dds/merge-tree/src/attributionPolicy.ts index f5c759924341..2377c4482f3b 100644 --- a/packages/dds/merge-tree/src/attributionPolicy.ts +++ b/packages/dds/merge-tree/src/attributionPolicy.ts @@ -3,8 +3,6 @@ * 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"; diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index 767958f199a4..374bb38d8260 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"; @@ -38,6 +37,7 @@ import { import { createAnnotateMarkerOp, createAnnotateRangeOp, + // eslint-disable-next-line import/no-deprecated createGroupOp, createInsertSegmentOp, createObliterateRangeOp, @@ -48,6 +48,7 @@ import { IJSONSegment, IMergeTreeAnnotateMsg, IMergeTreeDeltaOp, + // eslint-disable-next-line import/no-deprecated IMergeTreeGroupMsg, IMergeTreeInsertMsg, IMergeTreeRemoveMsg, @@ -99,7 +100,7 @@ export interface IClientEvents { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export class Client extends TypedEventEmitter { public longClientId: string | undefined; @@ -695,15 +696,6 @@ export class Client extends TypedEventEmitter { } } - // 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); @@ -711,16 +703,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"); } @@ -771,6 +766,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) { @@ -873,6 +872,7 @@ export class Client extends TypedEventEmitter { } public applyStashedOp(op: IMergeTreeDeltaOp): SegmentGroup; + // eslint-disable-next-line import/no-deprecated public applyStashedOp(op: IMergeTreeGroupMsg): SegmentGroup[]; public applyStashedOp(op: IMergeTreeOp): SegmentGroup | SegmentGroup[]; public applyStashedOp(op: IMergeTreeOp): SegmentGroup | SegmentGroup[] { @@ -923,7 +923,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( @@ -975,6 +975,7 @@ export class Client extends TypedEventEmitter { 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]; @@ -1029,6 +1030,7 @@ 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); } @@ -1092,6 +1094,7 @@ export class Client extends TypedEventEmitter { return segWindow.collaborating ? UnassignedSequenceNumber : UniversalSequenceNumber; } + // eslint-disable-next-line import/no-deprecated localTransaction(groupOp: IMergeTreeGroupMsg) { for (const op of groupOp.ops) { const opArgs: IMergeTreeDeltaOpArgs = { @@ -1116,7 +1119,8 @@ export class Client extends TypedEventEmitter { } } } - updateConsensusProperty(op: IMergeTreeAnnotateMsg, msg: ISequencedDocumentMessage) { + + private updateConsensusProperty(op: IMergeTreeAnnotateMsg, msg: ISequencedDocumentMessage) { const markerId = op.relativePos1!.id!; const consensusInfo = this.pendingConsensus.get(markerId); if (consensusInfo) { @@ -1155,6 +1159,7 @@ export class Client extends TypedEventEmitter { } return propertiesAtPosition; } + getRangeExtentsOfPosition(pos: number) { let posStart: number | undefined; let posAfterEnd: number | undefined; @@ -1167,9 +1172,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/constants.ts b/packages/dds/merge-tree/src/constants.ts index 27a0a05136ce..a4df61cf4c50 100644 --- a/packages/dds/merge-tree/src/constants.ts +++ b/packages/dds/merge-tree/src/constants.ts @@ -4,29 +4,24 @@ */ /** - * 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. * - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * This is useful in the context of snapshot loading, rollback, among other + * scenarios. + * + * @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 - */ export const TreeMaintenanceSequenceNumber = -2; -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ export const LocalClientId = -1; -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ 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 007613c8b537..00408d33f551 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -27,13 +27,7 @@ export { RedBlackTree, SortedDictionary, } from "./collections"; -export { - LocalClientId, - NonCollabClient, - TreeMaintenanceSequenceNumber, - UnassignedSequenceNumber, - UniversalSequenceNumber, -} from "./constants"; +export { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; export { createDetachedLocalReferencePosition, LocalReferenceCollection, @@ -52,21 +46,15 @@ export { IMergeTreeDeltaOpArgs, IMergeTreeMaintenanceCallbackArgs, IMergeTreeSegmentDelta, - MergeTreeDeltaCallback, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, - MergeTreeMaintenanceCallback, MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback"; export { BaseSegment, CollaborationWindow, - compareNumbers, - compareStrings, debugMarkerToString, - IConsensusInfo, IJSONMarkerSegment, - IMarkerModifiedAction, IMergeNodeCommon, IRemovalInfo, ISegment, @@ -75,7 +63,6 @@ export { MergeNode, reservedMarkerIdKey, reservedMarkerSimpleTypeKey, - SegmentAccumulator, SegmentGroup, toRemovalInfo, } from "./mergeTreeNodes"; @@ -86,7 +73,6 @@ export { TrackingGroupCollection, } from "./mergeTreeTracking"; export { - createAnnotateMarkerOp, createAnnotateRangeOp, createGroupOp, createInsertOp, @@ -108,14 +94,7 @@ export { MergeTreeDeltaType, ReferenceType, } from "./ops"; -export { - addProperties, - createMap, - IConsensusValue, - MapLike, - matchProperties, - PropertySet, -} from "./properties"; +export { addProperties, createMap, MapLike, matchProperties, PropertySet } from "./properties"; export { compareReferencePositions, DetachedReferencePosition, diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 63c57ee55192..3116cb0b1143 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -5,7 +5,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-bitwise */ -/* eslint-disable import/no-deprecated */ import { assert } from "@fluidframework/core-utils"; import { DataProcessingError, UsageError } from "@fluidframework/telemetry-utils"; @@ -422,6 +421,7 @@ export class MergeTree { public readonly collabWindow = new CollaborationWindow(); public readonly pendingSegments = new DoublyLinkedList(); + public readonly segmentsToScour = new Heap([], LRUSegmentComparer); public readonly attributionPolicy: AttributionPolicy | undefined; @@ -1181,12 +1181,11 @@ export class MergeTree { ) { let _segmentGroup = segmentGroup; if (_segmentGroup === undefined) { - // TODO: review the cast _segmentGroup = { segments: [], localSeq, refSeq: this.collabWindow.currentSeq, - } as any as SegmentGroup; + }; if (previousProps) { _segmentGroup.previousProps = []; } @@ -1655,7 +1654,7 @@ export class MergeTree { props, combiningOp, seq, - this.collabWindow, + this.collabWindow.collaborating, rollback, ); deltaSegments.push({ segment, propertyDeltas }); diff --git a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts index 8423c415d6a8..03dd820553a7 100644 --- a/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts +++ b/packages/dds/merge-tree/src/mergeTreeDeltaCallback.ts @@ -93,9 +93,6 @@ export interface IMergeTreeClientSequenceArgs { readonly sequenceNumber: number; } -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ export type MergeTreeDeltaCallback = ( opArgs: IMergeTreeDeltaOpArgs, deltaArgs: IMergeTreeDeltaCallbackArgs, @@ -105,9 +102,6 @@ export type MergeTreeDeltaCallback = ( export interface IMergeTreeMaintenanceCallbackArgs extends IMergeTreeDeltaCallbackArgs {} -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ export type MergeTreeMaintenanceCallback = ( MaintenanceArgs: IMergeTreeMaintenanceCallbackArgs, opArgs: IMergeTreeDeltaOpArgs | undefined, diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index a5cf24f0596e..c10899cd2bfc 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -4,7 +4,6 @@ */ /* 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"; @@ -106,7 +105,7 @@ 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 { if (maybe?.removedClientIds !== undefined && maybe?.removedSeq !== undefined) { @@ -264,7 +263,7 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti newProps: PropertySet, op?: ICombiningOp, seq?: number, - collabWindow?: CollaborationWindow, + collaborating?: boolean, rollback?: PropertiesRollback, ): PropertySet | undefined; clone(): ISegment; @@ -284,19 +283,11 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti * 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 + * @internal */ ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; } -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ -export interface IMarkerModifiedAction { - // eslint-disable-next-line @typescript-eslint/prefer-function-type - (marker: Marker): void; -} - export interface ISegmentAction { // eslint-disable-next-line @typescript-eslint/prefer-function-type ( @@ -386,12 +377,12 @@ export interface SearchResult { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export interface SegmentGroup { segments: ISegment[]; previousProps?: PropertySet[]; - localSeq: number; + localSeq?: number; refSeq: number; } @@ -483,7 +474,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { newProps: PropertySet, op?: ICombiningOp, seq?: number, - collabWindow?: CollaborationWindow, + collaborating?: boolean, rollback: PropertiesRollback = PropertiesRollback.None, ) { this.propertyManager ??= new PropertiesManager(); @@ -493,7 +484,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { newProps, op, seq, - collabWindow?.collaborating, + collaborating, rollback, ); } @@ -534,7 +525,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 + * @internal */ public ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean { const currentSegmentGroup = this.segmentGroups.dequeue(); @@ -739,7 +730,7 @@ export class Marker extends BaseSegment implements ReferencePosition { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export class CollaborationWindow { clientId = LocalClientId; @@ -760,14 +751,8 @@ export class CollaborationWindow { } } -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ 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 - */ export const compareStrings = (a: string, b: string) => a.localeCompare(b); export interface IConsensusInfo { @@ -775,12 +760,6 @@ export interface IConsensusInfo { callback: (m: Marker) => void; } -/** - * @deprecated This functionality was not meant to be exported and will be removed in a future release - */ -export interface SegmentAccumulator { - segments: ISegment[]; -} /** * @internal */ diff --git a/packages/dds/merge-tree/src/mergeTreeTracking.ts b/packages/dds/merge-tree/src/mergeTreeTracking.ts index 5626e644d906..8109d0692851 100644 --- a/packages/dds/merge-tree/src/mergeTreeTracking.ts +++ b/packages/dds/merge-tree/src/mergeTreeTracking.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { LocalReferencePosition } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; import { SortedSegmentSet } from "./sortedSegmentSet"; diff --git a/packages/dds/merge-tree/src/opBuilder.ts b/packages/dds/merge-tree/src/opBuilder.ts index 466dd93620c9..3e1a028f73b3 100644 --- a/packages/dds/merge-tree/src/opBuilder.ts +++ b/packages/dds/merge-tree/src/opBuilder.ts @@ -2,12 +2,12 @@ * 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, @@ -24,7 +24,7 @@ import { PropertySet } from "./properties"; * @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, @@ -53,7 +53,7 @@ export function createAnnotateMarkerOp( * @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, @@ -76,7 +76,7 @@ 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 { return { @@ -107,14 +107,14 @@ export function createObliterateRangeOp(start: number, end: number): IMergeTreeO * @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 { return createInsertOp(pos, segment.toJSONObject()); } /** - * @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 { return { @@ -128,11 +128,17 @@ 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, + // eslint-disable-next-line import/no-deprecated type: MergeTreeDeltaType.GROUP, }; } diff --git a/packages/dds/merge-tree/src/partialLengths.ts b/packages/dds/merge-tree/src/partialLengths.ts index ba675f73cc69..dadd88677f80 100644 --- a/packages/dds/merge-tree/src/partialLengths.ts +++ b/packages/dds/merge-tree/src/partialLengths.ts @@ -3,8 +3,6 @@ * 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"; diff --git a/packages/dds/merge-tree/src/properties.ts b/packages/dds/merge-tree/src/properties.ts index 951c3ee63c62..64641d0868cb 100644 --- a/packages/dds/merge-tree/src/properties.ts +++ b/packages/dds/merge-tree/src/properties.ts @@ -16,9 +16,6 @@ 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 - */ export interface IConsensusValue { seq: number; value: any; diff --git a/packages/dds/merge-tree/src/revertibles.ts b/packages/dds/merge-tree/src/revertibles.ts index a6acefd95b6d..4cf1907a3b40 100644 --- a/packages/dds/merge-tree/src/revertibles.ts +++ b/packages/dds/merge-tree/src/revertibles.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert, unreachableCase } from "@fluidframework/core-utils"; import { UsageError } from "@fluidframework/telemetry-utils"; import { DoublyLinkedList } from "./collections"; diff --git a/packages/dds/merge-tree/src/segmentGroupCollection.ts b/packages/dds/merge-tree/src/segmentGroupCollection.ts index 263d1f9d1a7a..16a90736d164 100644 --- a/packages/dds/merge-tree/src/segmentGroupCollection.ts +++ b/packages/dds/merge-tree/src/segmentGroupCollection.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { DoublyLinkedList, walkList } from "./collections"; import { ISegment, SegmentGroup } from "./mergeTreeNodes"; @@ -24,7 +22,7 @@ export class SegmentGroupCollection { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ public enqueue(segmentGroup: SegmentGroup) { this.segmentGroups.push(segmentGroup); @@ -32,14 +30,14 @@ export class SegmentGroupCollection { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ 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 + * @internal */ public pop?(): SegmentGroup | undefined { return this.segmentGroups.pop ? this.segmentGroups.pop()?.data : undefined; diff --git a/packages/dds/merge-tree/src/segmentPropertiesManager.ts b/packages/dds/merge-tree/src/segmentPropertiesManager.ts index 822513dbc192..495fcf3e5c1d 100644 --- a/packages/dds/merge-tree/src/segmentPropertiesManager.ts +++ b/packages/dds/merge-tree/src/segmentPropertiesManager.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { assert } from "@fluidframework/core-utils"; diff --git a/packages/dds/merge-tree/src/snapshotLoader.ts b/packages/dds/merge-tree/src/snapshotLoader.ts index 8af07a8bf9b2..90ae9a43359c 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"; diff --git a/packages/dds/merge-tree/src/snapshotV1.ts b/packages/dds/merge-tree/src/snapshotV1.ts index 6f6bf4e842f2..fab23c9263e8 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"; 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 33edf7544e76..bfeaeb0ec873 100644 --- a/packages/dds/merge-tree/src/sortedSegmentSet.ts +++ b/packages/dds/merge-tree/src/sortedSegmentSet.ts @@ -3,14 +3,12 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { LocalReferencePosition } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; import { SortedSet } from "./sortedSet"; /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export type SortedSegmentSetItem = | ISegment @@ -27,7 +25,7 @@ export type SortedSegmentSetItem = * the segments changes. This invariant allows us to ensure the segments stay * ordered and unique, and that new segments can be inserted into that order. * - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export class SortedSegmentSet extends SortedSet< T, diff --git a/packages/dds/merge-tree/src/sortedSet.ts b/packages/dds/merge-tree/src/sortedSet.ts index e79838f5f916..801b8c5cecff 100644 --- a/packages/dds/merge-tree/src/sortedSet.ts +++ b/packages/dds/merge-tree/src/sortedSet.ts @@ -4,7 +4,7 @@ */ /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export abstract class SortedSet { protected abstract getKey(t: T): U; diff --git a/packages/dds/merge-tree/src/test/beastTest.ts b/packages/dds/merge-tree/src/test/beastTest.ts index 423cf99a5572..f3d57235acef 100644 --- a/packages/dds/merge-tree/src/test/beastTest.ts +++ b/packages/dds/merge-tree/src/test/beastTest.ts @@ -24,7 +24,14 @@ import { SortedDictionary, } from "../collections"; import { LocalClientId, UnassignedSequenceNumber, UniversalSequenceNumber } from "../constants"; -import { IJSONMarkerSegment, IMergeNode, ISegment, reservedMarkerIdKey } from "../mergeTreeNodes"; +import { + IJSONMarkerSegment, + IMergeNode, + ISegment, + reservedMarkerIdKey, + compareNumbers, + compareStrings, +} from "../mergeTreeNodes"; import { IMergeTreeDeltaOpArgs } from "../mergeTreeDeltaCallback"; import { createRemoveRangeOp } from "../opBuilder"; import { IMergeTreeOp, MergeTreeDeltaType, ReferenceType } from "../ops"; @@ -139,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; diff --git a/packages/dds/merge-tree/src/test/index.ts b/packages/dds/merge-tree/src/test/index.ts index 6c3bffad0bc9..57ca337244a2 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -47,11 +47,8 @@ export { BaseSegment, Client, CollaborationWindow, - compareNumbers, compareReferencePositions, - compareStrings, ConflictAction, - createAnnotateMarkerOp, createAnnotateRangeOp, createDetachedLocalReferencePosition, createGroupOp, @@ -61,16 +58,11 @@ export { createRemoveRangeOp, debugMarkerToString, DetachedReferencePosition, - Dictionary, discardMergeTreeDeltaRevertible, ICombiningOp, - IConsensusInfo, - IConsensusValue, IJSONMarkerSegment, IJSONSegment, - IJSONTextSegment, IMarkerDef, - IMarkerModifiedAction, IMergeNodeCommon, IMergeTreeAnnotateMsg, IMergeTreeClientSequenceArgs, @@ -92,7 +84,6 @@ export { ISegment, ISegmentAction, KeyComparer, - LocalClientId, LocalReferenceCollection, LocalReferencePosition, MapLike, @@ -100,23 +91,17 @@ export { matchProperties, maxReferencePosition, MergeNode, - MergeTreeDeltaCallback, MergeTreeDeltaOperationType, MergeTreeDeltaOperationTypes, MergeTreeDeltaRevertible, MergeTreeDeltaType, - MergeTreeMaintenanceCallback, MergeTreeMaintenanceType, MergeTreeRevertibleDriver, minReferencePosition, - NonCollabClient, PropertiesManager, - PropertiesRollback, Property, PropertyAction, PropertySet, - QProperty, - RBColor, RBNode, RBNodeActions, RedBlackTree, @@ -130,10 +115,8 @@ export { reservedMarkerSimpleTypeKey, reservedTileLabelsKey, revertMergeTreeDeltaRevertibles, - SegmentAccumulator, SegmentGroup, SegmentGroupCollection, - SortedDictionary, SortedSegmentSet, SortedSegmentSetItem, SortedSet, @@ -142,7 +125,6 @@ export { Trackable, TrackingGroup, TrackingGroupCollection, - TreeMaintenanceSequenceNumber, UnassignedSequenceNumber, UniversalSequenceNumber, } from ".."; 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 670e0391e9e4..e39dff1f3bd8 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -291,51 +291,26 @@ use_old_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); -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); -use_old_InterfaceDeclaration_IConsensusInfo( - // @ts-expect-error compatibility expected to be broken - 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); -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); -use_old_InterfaceDeclaration_IConsensusValue( - get_current_InterfaceDeclaration_IConsensusValue()); /* * Validate forward compat by using old type in place of current type @@ -448,26 +423,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); -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); -use_old_InterfaceDeclaration_IMarkerModifiedAction( - get_current_InterfaceDeclaration_IMarkerModifiedAction()); /* * Validate forward compat by using old type in place of current type @@ -1048,26 +1011,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); -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); -use_old_VariableDeclaration_LocalClientId( - get_current_VariableDeclaration_LocalClientId()); /* * Validate forward compat by using old type in place of current type @@ -1194,26 +1145,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); -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); -use_old_TypeAliasDeclaration_MergeTreeDeltaCallback( - get_current_TypeAliasDeclaration_MergeTreeDeltaCallback()); /* * Validate forward compat by using old type in place of current type @@ -1339,26 +1278,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); -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); -use_old_TypeAliasDeclaration_MergeTreeMaintenanceCallback( - get_current_TypeAliasDeclaration_MergeTreeMaintenanceCallback()); /* * Validate forward compat by using old type in place of current type @@ -1435,26 +1362,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); -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); -use_old_VariableDeclaration_NonCollabClient( - get_current_VariableDeclaration_NonCollabClient()); /* * Validate forward compat by using old type in place of current type @@ -1783,26 +1698,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); -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); -use_old_InterfaceDeclaration_SegmentAccumulator( - get_current_InterfaceDeclaration_SegmentAccumulator()); /* * Validate forward compat by using old type in place of current type @@ -1826,6 +1729,7 @@ declare function get_current_InterfaceDeclaration_SegmentGroup(): declare function use_old_InterfaceDeclaration_SegmentGroup( use: TypeOnly); use_old_InterfaceDeclaration_SegmentGroup( + // @ts-expect-error compatibility expected to be broken get_current_InterfaceDeclaration_SegmentGroup()); /* @@ -2156,26 +2060,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); -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); -use_old_VariableDeclaration_TreeMaintenanceSequenceNumber( - get_current_VariableDeclaration_TreeMaintenanceSequenceNumber()); /* * Validate forward compat by using old type in place of current type @@ -2300,26 +2192,14 @@ 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: -* "VariableDeclaration_compareNumbers": {"forwardCompat": false} +* "RemovedVariableDeclaration_compareNumbers": {"forwardCompat": false} */ -declare function get_old_VariableDeclaration_compareNumbers(): - TypeOnly; -declare function use_current_VariableDeclaration_compareNumbers( - use: TypeOnly); -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); -use_old_VariableDeclaration_compareNumbers( - get_current_VariableDeclaration_compareNumbers()); /* * Validate forward compat by using old type in place of current type @@ -2348,50 +2228,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); -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); -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); -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); -use_old_FunctionDeclaration_createAnnotateMarkerOp( - get_current_FunctionDeclaration_createAnnotateMarkerOp()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/dds/merge-tree/src/textSegment.ts b/packages/dds/merge-tree/src/textSegment.ts index ba36ec051b95..6b7a6afc2d9d 100644 --- a/packages/dds/merge-tree/src/textSegment.ts +++ b/packages/dds/merge-tree/src/textSegment.ts @@ -55,7 +55,7 @@ export class TextSegment extends BaseSegment { 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; @@ -87,22 +87,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); @@ -115,7 +99,7 @@ export class TextSegment extends BaseSegment { } /** - * @deprecated This functionality was not meant to be exported and will be removed in a future release + * @internal */ export interface IMergeTreeTextHelper { getText( diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 4511267e9f93..340a3c1c169a 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"; diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index c4a7c9822f5f..ce8a1cd4e82f 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -22,7 +22,6 @@ 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'; @@ -597,8 +596,6 @@ export abstract class SharedSegmentSequence extends SharedOb 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; diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index beed2e9097fc..c6a98c1ec995 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -129,8 +129,11 @@ "backCompat": false }, "ClassDeclaration_SharedString": { - "backCompat": false, - "forwardCompat": false + "forwardCompat": false, + "backCompat": false + }, + "TypeAliasDeclaration_SharedStringSegment": { + "backCompat": false } } } diff --git a/packages/dds/sequence/src/revertibles.ts b/packages/dds/sequence/src/revertibles.ts index f086349257e0..957dab51e035 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"; diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 0d23826b78da..c282def3433f 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"; @@ -17,6 +15,7 @@ import { import { Client, createAnnotateRangeOp, + // eslint-disable-next-line import/no-deprecated createGroupOp, createInsertOp, createRemoveRangeOp, @@ -397,10 +396,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; } @@ -757,6 +753,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 452e607539ca..e5680850f461 100644 --- a/packages/dds/sequence/src/sequenceDeltaEvent.ts +++ b/packages/dds/sequence/src/sequenceDeltaEvent.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { Client, diff --git a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index d4394a8a3164..ead9b8f4b929 100644 --- a/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts +++ b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts @@ -1080,6 +1080,7 @@ declare function get_current_TypeAliasDeclaration_SharedStringSegment(): declare function use_old_TypeAliasDeclaration_SharedStringSegment( use: TypeOnly); use_old_TypeAliasDeclaration_SharedStringSegment( + // @ts-expect-error compatibility expected to be broken get_current_TypeAliasDeclaration_SharedStringSegment()); /* From b989018827f339cce6bfc3730e52d8cca2250ca0 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:58:04 -0700 Subject: [PATCH 20/50] refactor(merge-tree sequence): minor cleanup (#18129) Minor cleanup to merge-tree and sequence. See individual commits for more context. Largely deletes code that has now become unused after several previous removals. --- .changeset/every-suns-bow.md | 8 +++++ .../merge-tree/api-report/merge-tree.api.md | 4 +-- .../dds/merge-tree/src/MergeTreeTextHelper.ts | 2 +- packages/dds/merge-tree/src/base.ts | 13 -------- packages/dds/merge-tree/src/client.ts | 14 ++++++-- packages/dds/merge-tree/src/mergeTree.ts | 24 ++++---------- packages/dds/merge-tree/src/mergeTreeNodes.ts | 32 ------------------- packages/dds/merge-tree/src/ops.ts | 4 ++- .../dds/merge-tree/src/test/testClient.ts | 9 +++--- .../merge-tree/src/test/testClientLogger.ts | 3 +- packages/dds/merge-tree/src/test/testUtils.ts | 9 +++--- .../dds/sequence/api-report/sequence.api.md | 10 +++--- packages/dds/sequence/src/sequence.ts | 18 ++++++----- packages/dds/sequence/src/sharedString.ts | 32 ++++++++----------- .../dds/sequence/src/test/intervalUtils.ts | 2 +- 15 files changed, 71 insertions(+), 113 deletions(-) create mode 100644 .changeset/every-suns-bow.md delete mode 100644 packages/dds/merge-tree/src/base.ts 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/packages/dds/merge-tree/api-report/merge-tree.api.md b/packages/dds/merge-tree/api-report/merge-tree.api.md index 64db0c44659a..f57147be6336 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -99,7 +99,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // @internal (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; @@ -650,8 +650,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; diff --git a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts index 159cb4c31fd5..5d634a243dd4 100644 --- a/packages/dds/merge-tree/src/MergeTreeTextHelper.ts +++ b/packages/dds/merge-tree/src/MergeTreeTextHelper.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { IIntegerRange } from "./base"; +import { IIntegerRange } from "./client"; import { ISegment } from "./mergeTreeNodes"; import { MergeTree } from "./mergeTree"; import { IMergeTreeTextHelper, TextSegment } from "./textSegment"; diff --git a/packages/dds/merge-tree/src/base.ts b/packages/dds/merge-tree/src/base.ts deleted file mode 100644 index 32f70ddd6235..000000000000 --- a/packages/dds/merge-tree/src/base.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -/** - * A range [start, end) - * @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 374bb38d8260..265f1790569c 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -16,7 +16,6 @@ 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 { DoublyLinkedList, RedBlackTree } from "./collections"; import { UnassignedSequenceNumber, UniversalSequenceNumber } from "./constants"; import { LocalReferencePosition, SlidingPreference } from "./localReference"; @@ -64,7 +63,7 @@ import { SnapshotLoader } from "./snapshotLoader"; import { IMergeTreeTextHelper } from "./textSegment"; import { SnapshotV1 } from "./snapshotV1"; import { ReferencePosition, DetachedReferencePosition } from "./referencePositions"; -import { MergeTree } from "./mergeTree"; +import { IMergeTreeOptions, MergeTree } from "./mergeTree"; import { MergeTreeTextHelper } from "./MergeTreeTextHelper"; import { walkAllChildSegments } from "./mergeTreeNodeWalk"; import { IMergeTreeClientSequenceArgs, IMergeTreeDeltaOpArgs } from "./index"; @@ -72,6 +71,15 @@ import { IMergeTreeClientSequenceArgs, IMergeTreeDeltaOpArgs } from "./index"; type IMergeTreeDeltaRemoteOpArgs = Omit & Required>; +/** + * 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 @@ -115,7 +123,7 @@ export class Client extends TypedEventEmitter { // 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); diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 3116cb0b1143..8c567ffdb407 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -1481,14 +1481,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()) { @@ -1504,11 +1505,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 @@ -1530,9 +1527,6 @@ export class MergeTree { childIndex++; // Insert after } else { // No change - if (context.structureChange) { - this.nodeUpdateLengthNewStructure(block); - } return undefined; } } @@ -1564,11 +1558,7 @@ 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 @@ -1664,7 +1654,7 @@ export class MergeTree { segment, segmentGroup, localSeq, - propertyDeltas ? propertyDeltas : {}, + propertyDeltas ?? {}, ); } else { if (MergeTree.options.zamboniSegments) { diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index c10899cd2bfc..9431ea36eebe 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -339,43 +339,15 @@ export interface NodeAction { ): boolean; } -/** - * @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; } -/** - * @internal - */ -export interface SegmentActions { - leaf?: ISegmentAction; - shift?: NodeAction; - contains?: NodeAction; - pre?: BlockAction; - post?: BlockAction; -} - -/** - * @internal - */ -export interface SearchResult { - text: string; - pos: number; -} - /** * @internal */ @@ -700,10 +672,6 @@ export class Marker extends BaseSegment implements ReferencePosition { return 0; } - hasSimpleType(simpleTypeName: string) { - return !!this.properties && this.properties[reservedMarkerSimpleTypeKey] === simpleTypeName; - } - getProperties() { return this.properties; } diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index dba34835e784..a69354ab13c8 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -129,7 +129,9 @@ export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { } /** - * @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 */ export interface IMergeTreeGroupMsg extends IMergeTreeDelta { type: typeof MergeTreeDeltaType.GROUP; diff --git a/packages/dds/merge-tree/src/test/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index 9007a2f9d82c..6b74872b065e 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -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"; @@ -139,7 +139,7 @@ export class TestClient extends Client { 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); @@ -448,8 +448,9 @@ export class TestClient extends Client { return true; }); - assert( - fasterComputedPosition === segmentPosition, + assert.equal( + fasterComputedPosition, + segmentPosition, "Expected fast-path computation to match result from walk all segments", ); return segmentPosition; 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/testUtils.ts b/packages/dds/merge-tree/src/test/testUtils.ts index 90c513909391..1fedf8584719 100644 --- a/packages/dds/merge-tree/src/test/testUtils.ts +++ b/packages/dds/merge-tree/src/test/testUtils.ts @@ -12,6 +12,7 @@ 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"; @@ -180,19 +181,19 @@ 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)); walkAllChildSegments(mergeBlock, (segment) => { if (isInserted(segment) && !isRemoved(segment)) { diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index ce8a1cd4e82f..5bb839fc9aba 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -20,9 +20,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 { IMergeTreeRemoveMsg } from '@fluidframework/merge-tree'; import { IRelativePosition } from '@fluidframework/merge-tree'; import { ISegment } from '@fluidframework/merge-tree'; import { ISegmentAction } from '@fluidframework/merge-tree'; @@ -375,7 +373,7 @@ export interface ISharedSegmentSequenceEvents extends ISharedObjectEvents { // @public 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; } @@ -589,7 +587,7 @@ 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) @@ -628,13 +626,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; diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index c282def3433f..e93563d655b0 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -265,12 +265,14 @@ 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 + * @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)); @@ -318,7 +320,7 @@ export abstract class SharedSegmentSequence end: number, props: PropertySet, combiningOp?: ICombiningOp, - ) { + ): void { this.guardReentrancy(() => this.client.annotateRangeLocal(start, end, props, combiningOp)); } @@ -407,7 +409,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 { @@ -461,7 +463,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)); } /** @@ -469,7 +471,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)); } @@ -539,7 +541,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); diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 3f2994a605bf..9b62b370cdb5 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -5,8 +5,6 @@ import { ICombiningOp, - IMergeTreeInsertMsg, - IMergeTreeRemoveMsg, IMergeTreeTextHelper, IRelativePosition, ISegment, @@ -40,11 +38,7 @@ export interface ISharedString extends SharedSegmentSequence this.client.insertSegmentLocal(pos, segment)); + this.guardReentrancy(() => this.client.insertSegmentLocal(pos, segment)); } /** @@ -147,7 +137,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 +154,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 +170,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,8 +180,8 @@ 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); + public removeText(start: number, end: number): void { + this.removeRange(start, end); } /** diff --git a/packages/dds/sequence/src/test/intervalUtils.ts b/packages/dds/sequence/src/test/intervalUtils.ts index 3900c725c4c5..04e72599ae09 100644 --- a/packages/dds/sequence/src/test/intervalUtils.ts +++ b/packages/dds/sequence/src/test/intervalUtils.ts @@ -31,8 +31,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( From f19150b80f3ac0d26b2d17e3f5c38e44b93977b3 Mon Sep 17 00:00:00 2001 From: jzaffiro <110866475+jzaffiro@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:21:09 -0800 Subject: [PATCH 21/50] Revert "Remove the findTile API" (#18210) Reverts microsoft/FluidFramework#17997, which should be removed in a later version --- .../webflow/src/document/index.ts | 22 +- .../merge-tree/api-report/merge-tree.api.md | 5 + packages/dds/merge-tree/src/client.ts | 8 + packages/dds/merge-tree/src/mergeTree.ts | 223 +++++++++++++++++ packages/dds/merge-tree/src/mergeTreeNodes.ts | 11 + packages/dds/merge-tree/src/ops.ts | 2 +- .../src/test/client.annotateMarker.spec.ts | 46 ---- .../dds/merge-tree/src/test/client.spec.ts | 224 ++++++++++++++++++ .../dds/merge-tree/src/test/wordUnitTests.ts | 8 +- .../dds/sequence/api-report/sequence.api.md | 5 + packages/dds/sequence/src/sharedString.ts | 23 ++ 11 files changed, 515 insertions(+), 62 deletions(-) delete mode 100644 packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts create mode 100644 packages/dds/merge-tree/src/test/client.spec.ts diff --git a/examples/data-objects/webflow/src/document/index.ts b/examples/data-objects/webflow/src/document/index.ts index bf78b7ee9b8e..700fa3581093 100644 --- a/examples/data-objects/webflow/src/document/index.ts +++ b/examples/data-objects/webflow/src/document/index.ts @@ -416,20 +416,20 @@ export class FlowDocument extends LazyLoadedDataObject { // (undocumented) createTextHelper(): IMergeTreeTextHelper; findReconnectionPosition(segment: ISegment, localSeq: number): number; + // @deprecated (undocumented) + findTile(startPos: number, tileLabel: string, preceding?: boolean): { + tile: ReferencePosition; + pos: number; + } | undefined; // (undocumented) getClientId(): number; // (undocumented) diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index 265f1790569c..cc538ac1916e 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -1218,6 +1218,14 @@ export class Client extends TypedEventEmitter { } } + /** + * @deprecated Use searchForMarker instead. + */ + findTile(startPos: number, tileLabel: string, preceding = true) { + const clientId = this.getClientId(); + return this._mergeTree.findTile(startPos, clientId, tileLabel, preceding); + } + /** * Searches a string for the nearest marker in either direction to a given start position. * The search will include the start position, so markers at the start position are valid diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 8c567ffdb407..78673b8a1920 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -40,6 +40,7 @@ import { MergeBlock, MinListener, reservedMarkerIdKey, + SegmentActions, SegmentGroup, seqLTE, toRemovalInfo, @@ -118,6 +119,59 @@ const LRUSegmentComparer: Comparer = { compare: (a, b) => a.maxSeq - b.maxSeq, }; +interface IReferenceSearchInfo { + mergeTree: MergeTree; + tileLabel: string; + tilePrecedesPos?: boolean; + tile?: ReferencePosition; +} + +function recordTileStart( + segment: ISegment, + segpos: number, + refSeq: number, + clientId: number, + start: number, + end: number, + searchInfo: IReferenceSearchInfo, +) { + if (Marker.is(segment)) { + if (refHasTileLabel(segment, searchInfo.tileLabel)) { + searchInfo.tile = segment; + } + } + return false; +} + +function tileShift( + node: IMergeNode, + segpos: number, + refSeq: number, + clientId: number, + offset: number | undefined, + end: number | undefined, + searchInfo: IReferenceSearchInfo, +) { + if (node.isLeaf()) { + const seg = node; + if ((searchInfo.mergeTree.localNetLength(seg) ?? 0) > 0 && Marker.is(seg)) { + if (refHasTileLabel(seg, searchInfo.tileLabel)) { + searchInfo.tile = seg; + } + } + } else { + const block = node as IHierBlock; + const marker = searchInfo.tilePrecedesPos + ? 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) { const tileLabels = refGetTileLabels(tile); if (tileLabels) { @@ -1055,6 +1109,60 @@ export class MergeTree { return DetachedReferencePosition; } + // TODO: filter function + /** + * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`. + * @deprecated Use searchForMarker instead. + * + * @param startPos - Position at which to start the search + * @param clientId - clientId dictating the perspective to search from + * @param tileLabel - Label of the tile to search for + * @param tilePrecedesPos - Whether the desired tile comes before (true) or after (false) `startPos` + */ + public findTile(startPos: number, clientId: number, tileLabel: string, tilePrecedesPos = true) { + const searchInfo: IReferenceSearchInfo = { + mergeTree: this, + tilePrecedesPos, + tileLabel, + }; + + if (tilePrecedesPos) { + this.search( + startPos, + UniversalSequenceNumber, + clientId, + { leaf: recordTileStart, shift: tileShift }, + searchInfo, + ); + } else { + this.backwardSearch( + startPos, + UniversalSequenceNumber, + clientId, + { leaf: recordTileStart, shift: tileShift }, + searchInfo, + ); + } + + if (searchInfo.tile) { + let pos: number; + if (searchInfo.tile.isLeaf()) { + const marker = searchInfo.tile as Marker; + pos = this.getPosition(marker, UniversalSequenceNumber, clientId); + } else { + const localRef = searchInfo.tile; + pos = this.referencePositionToLocalPosition( + localRef, + UniversalSequenceNumber, + clientId, + ); + } + return { tile: searchInfo.tile, pos }; + } + + return undefined; + } + /** * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `forwards`. * Uses depthFirstNodeWalk in addition to block-accelerated functionality. The search position will be included in @@ -1112,6 +1220,121 @@ export class MergeTree { return foundMarker; } + private search( + pos: number, + refSeq: number, + clientId: number, + actions: SegmentActions | undefined, + clientData: TClientData, + ): ISegment | undefined { + return this.searchBlock(this.root, pos, 0, refSeq, clientId, actions, clientData); + } + + private searchBlock( + block: IMergeBlock, + pos: number, + segpos: number, + refSeq: number, + clientId: number, + actions: SegmentActions | undefined, + clientData: TClientData, + localSeq?: number, + ): ISegment | undefined { + const { pre, post, contains, leaf, shift } = actions ?? {}; + let _pos = pos; + let _segpos = segpos; + const children = block.children; + pre?.(block, _segpos, refSeq, clientId, undefined, undefined, clientData); + for (let childIndex = 0; childIndex < block.childCount; childIndex++) { + const child = children[childIndex]; + const len = this.nodeLength(child, refSeq, clientId, localSeq) ?? 0; + if ( + (!contains && _pos < len) || + contains?.(child, _pos, refSeq, clientId, undefined, undefined, clientData) + ) { + // Found entry containing pos + if (!child.isLeaf()) { + return this.searchBlock( + child, + _pos, + _segpos, + refSeq, + clientId, + actions, + clientData, + localSeq, + ); + } else { + leaf?.(child, _segpos, refSeq, clientId, _pos, -1, clientData); + return child; + } + } else { + shift?.(child, _segpos, refSeq, clientId, _pos, undefined, clientData); + _pos -= len; + _segpos += len; + } + } + post?.(block, _segpos, refSeq, clientId, undefined, undefined, clientData); + } + + private backwardSearch( + pos: number, + refSeq: number, + clientId: number, + actions: SegmentActions | undefined, + clientData: TClientData, + ): ISegment | undefined { + const len = this.getLength(refSeq, clientId); + if (pos > len) { + return undefined; + } + return this.backwardSearchBlock(this.root, pos, len, refSeq, clientId, actions, clientData); + } + + private backwardSearchBlock( + block: IMergeBlock, + pos: number, + segEnd: number, + refSeq: number, + clientId: number, + actions: SegmentActions | undefined, + clientData: TClientData, + ): ISegment | undefined { + const { pre, post, contains, leaf, shift } = actions ?? {}; + let _segEnd = segEnd; + const children = block.children; + pre?.(block, _segEnd, refSeq, clientId, undefined, undefined, clientData); + for (let childIndex = block.childCount - 1; childIndex >= 0; childIndex--) { + const child = children[childIndex]; + const len = this.nodeLength(child, refSeq, clientId) ?? 0; + const segpos = _segEnd - len; + if ( + (!contains && pos >= segpos) || + contains?.(child, pos, refSeq, clientId, undefined, undefined, clientData) + ) { + // Found entry containing pos + if (!child.isLeaf()) { + return this.backwardSearchBlock( + child, + pos, + _segEnd, + refSeq, + clientId, + actions, + clientData, + ); + } else { + leaf?.(child, segpos, refSeq, clientId, pos, -1, clientData); + return child; + } + } else { + shift?.(child, segpos, refSeq, clientId, pos, undefined, clientData); + _segEnd = segpos; + } + } + post?.(block, _segEnd, refSeq, clientId, undefined, undefined, clientData); + } + private updateRoot(splitNode: IMergeBlock | undefined) { if (splitNode !== undefined) { const newRoot = this.makeBlock(2); diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 9431ea36eebe..bbdcfdcd7a32 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -348,6 +348,17 @@ export interface InsertContext { continuePredicate?: (continueFromBlock: IMergeBlock) => boolean; } +/** + * @internal + */ +export interface SegmentActions { + leaf?: ISegmentAction; + shift?: NodeAction; + contains?: NodeAction; + pre?: BlockAction; + post?: BlockAction; +} + /** * @internal */ diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index a69354ab13c8..ff8fe4299885 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -9,7 +9,7 @@ export enum ReferenceType { Simple = 0x0, /** - * Allows this reference to be located using the `searchForMarker` API on merge-tree. + * Allows this reference to be located using the `findTile` API on merge-tree. */ Tile = 0x1, RangeBegin = 0x10, diff --git a/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts b/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts deleted file mode 100644 index 1d0a3bd87969..000000000000 --- a/packages/dds/merge-tree/src/test/client.annotateMarker.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { strict as assert } from "assert"; -import { UniversalSequenceNumber } from "../constants"; -import { Marker, reservedMarkerIdKey } from "../mergeTreeNodes"; -import { ReferenceType } from "../ops"; -import { TextSegment } from "../textSegment"; -import { TestClient } from "./testClient"; -import { insertSegments } from "./testUtils"; - -describe("TestClient", () => { - const localUserLongId = "localUser"; - let client: TestClient; - - beforeEach(() => { - client = new TestClient(); - insertSegments({ - mergeTree: client.mergeTree, - pos: 0, - segments: [TextSegment.make("")], - refSeq: UniversalSequenceNumber, - clientId: client.getClientId(), - seq: UniversalSequenceNumber, - opArgs: undefined, - }); - client.startOrUpdateCollaboration(localUserLongId); - }); - - describe(".annotateMarker", () => { - it("annotate valid marker", () => { - const insertOp = client.insertMarkerLocal(0, ReferenceType.Tile, { - [reservedMarkerIdKey]: "123", - }); - assert(insertOp); - const markerInfo = client.getContainingSegment(0); - const marker = markerInfo.segment as Marker; - const annotateOp = client.annotateMarker(marker, { foo: "bar" }, undefined); - assert(annotateOp); - assert(marker.properties); - assert(marker.properties.foo, "bar"); - }); - }); -}); diff --git a/packages/dds/merge-tree/src/test/client.spec.ts b/packages/dds/merge-tree/src/test/client.spec.ts new file mode 100644 index 000000000000..f5ccd40b8e02 --- /dev/null +++ b/packages/dds/merge-tree/src/test/client.spec.ts @@ -0,0 +1,224 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "assert"; +import { UniversalSequenceNumber } from "../constants"; +import { Marker, reservedMarkerIdKey } from "../mergeTreeNodes"; +import { ReferenceType } from "../ops"; +import { reservedTileLabelsKey } from "../referencePositions"; +import { TextSegment } from "../textSegment"; +import { TestClient } from "./testClient"; +import { insertSegments } from "./testUtils"; + +describe("TestClient", () => { + const localUserLongId = "localUser"; + let client: TestClient; + + beforeEach(() => { + client = new TestClient(); + insertSegments({ + mergeTree: client.mergeTree, + pos: 0, + segments: [TextSegment.make("")], + refSeq: UniversalSequenceNumber, + clientId: client.getClientId(), + seq: UniversalSequenceNumber, + opArgs: undefined, + }); + client.startOrUpdateCollaboration(localUserLongId); + }); + + describe(".findTile", () => { + it("Should be able to find non preceding tile based on label", () => { + const tileLabel = "EOP"; + + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(0, "abc"); + + console.log(client.getText()); + + assert.equal(client.getLength(), 4, "length not expected"); + + const tile = client.findTile(0, tileLabel, false); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 3, "Tile with label not at expected position"); + }); + + it("Should be able to find non preceding tile position based on label from client with single tile", () => { + const tileLabel = "EOP"; + client.insertTextLocal(0, "abc d"); + + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + console.log(client.getText()); + + assert.equal(client.getLength(), 6, "length not expected"); + + const tile = client.findTile(0, tileLabel, false); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 0, "Tile with label not at expected position"); + }); + + it("Should be able to find preceding tile position based on label from client with multiple tile", () => { + const tileLabel = "EOP"; + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(0, "abc d"); + + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(7, "ef"); + client.insertMarkerLocal(8, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + console.log(client.getText()); + + assert.equal(client.getLength(), 10, "length not expected"); + + const tile = client.findTile(5, tileLabel); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 0, "Tile with label not at expected position"); + }); + + it("Should be able to find non preceding tile position from client with multiple tile", () => { + const tileLabel = "EOP"; + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(0, "abc d"); + + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(7, "ef"); + client.insertMarkerLocal(8, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + console.log(client.getText()); + + assert.equal(client.getLength(), 10, "length not expected"); + + const tile = client.findTile(5, tileLabel, false); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 6, "Tile with label not at expected position"); + }); + + it("Should be able to find tile from client with text length 1", () => { + const tileLabel = "EOP"; + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + console.log(client.getText()); + + assert.equal(client.getLength(), 1, "length not expected"); + + const tile = client.findTile(client.getLength(), tileLabel); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 0, "Tile with label not at expected position"); + + const tile1 = client.findTile(0, tileLabel, false); + + assert(tile1, "Returned tile undefined."); + + assert.equal(tile1.pos, 0, "Tile with label not at expected position"); + }); + + it("Should be able to find only preceding but not non preceding tile with index out of bound", () => { + const tileLabel = "EOP"; + client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedTileLabelsKey]: [tileLabel], + [reservedMarkerIdKey]: "some-id", + }); + + client.insertTextLocal(0, "abc"); + console.log(client.getText()); + + assert.equal(client.getLength(), 4, "length not expected"); + + const tile = client.findTile(5, tileLabel); + + assert(tile, "Returned tile undefined."); + + assert.equal(tile.pos, 3, "Tile with label not at expected position"); + + const tile1 = client.findTile(5, tileLabel, false); + + assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); + }); + + it("Should return undefined when trying to find tile from text without the specified tile", () => { + const tileLabel = "EOP"; + client.insertTextLocal(0, "abc"); + console.log(client.getText()); + + assert.equal(client.getLength(), 3, "length not expected"); + + const tile = client.findTile(1, tileLabel); + + assert.equal(typeof tile, "undefined", "Returned tile should be undefined."); + + const tile1 = client.findTile(1, tileLabel, false); + + assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); + }); + + it("Should return undefined when trying to find tile from null text", () => { + const tileLabel = "EOP"; + + const tile = client.findTile(1, tileLabel); + + assert.equal(typeof tile, "undefined", "Returned tile should be undefined."); + + const tile1 = client.findTile(1, tileLabel, false); + + assert.equal(typeof tile1, "undefined", "Returned tile should be undefined."); + }); + }); + + describe(".annotateMarker", () => { + it("annotate valid marker", () => { + const insertOp = client.insertMarkerLocal(0, ReferenceType.Tile, { + [reservedMarkerIdKey]: "123", + }); + assert(insertOp); + const markerInfo = client.getContainingSegment(0); + const marker = markerInfo.segment as Marker; + const annotateOp = client.annotateMarker(marker, { foo: "bar" }, undefined); + assert(annotateOp); + assert(marker.properties); + assert(marker.properties.foo, "bar"); + }); + }); +}); diff --git a/packages/dds/merge-tree/src/test/wordUnitTests.ts b/packages/dds/merge-tree/src/test/wordUnitTests.ts index 29c0df60ac75..3bb01df23d94 100644 --- a/packages/dds/merge-tree/src/test/wordUnitTests.ts +++ b/packages/dds/merge-tree/src/test/wordUnitTests.ts @@ -156,16 +156,16 @@ function measureFetch(startFile: string, withBookmarks = false) { let count = 0; for (let i = 0; i < reps; i++) { for (let pos = 0; pos < client.getLength(); ) { - // let prevPG = client.searchForMarker(pos, "pg", false); + // let prevPG = client.findTile(pos, "pg"); // let caBegin: number; // if (prevPG) { - // caBegin = client.localReferencePositionToPosition(prevPG); + // caBegin = prevPG.pos; // } else { // caBegin = 0; // } // curPG.pos is ca end - const curPG = client.searchForMarker(pos, "pg", true)!; - const properties = curPG.properties!; + const curPG = client.findTile(pos, "pg", false)!; + const properties = curPG.tile.properties!; const curSegOff = client.getContainingSegment(pos)!; const curSeg = curSegOff.segment!; // Combine paragraph and direct properties diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 5bb839fc9aba..f401fea76dda 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -618,6 +618,11 @@ export class SharedString extends SharedSegmentSequence imp annotateMarker(marker: Marker, props: PropertySet, combiningOp?: ICombiningOp): void; annotateMarkerNotifyConsensus(marker: Marker, props: PropertySet, callback: (m: Marker) => void): void; static create(runtime: IFluidDataStoreRuntime, id?: string): SharedString; + // @deprecated + findTile(startPos: number | undefined, tileLabel: string, preceding?: boolean): { + tile: ReferencePosition; + pos: number; + } | undefined; static getFactory(): SharedStringFactory; getMarkerFromId(id: string): ISegment | undefined; getText(start?: number, end?: number): string; diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index 9b62b370cdb5..820f35bfa831 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -11,6 +11,7 @@ import { ISegmentAction, Marker, PropertySet, + ReferencePosition, ReferenceType, refHasTileLabel, TextSegment, @@ -210,6 +211,28 @@ export class SharedString this.guardReentrancy(() => this.client.annotateMarker(marker, props, combiningOp)); } + /** + * Finds the nearest reference with ReferenceType.Tile to `startPos` in the direction dictated by `tilePrecedesPos`. + * Note that Markers receive `ReferenceType.Tile` by default. + * @deprecated Use `searchForMarker` instead. + * @param startPos - Position at which to start the search + * @param clientId - clientId dictating the perspective to search from + * @param tileLabel - Label of the tile to search for + * @param preceding - Whether the desired tile comes before (true) or after (false) `startPos` + */ + public findTile( + startPos: number | undefined, + tileLabel: string, + preceding = true, + ): + | { + tile: ReferencePosition; + pos: number; + } + | undefined { + return this.client.findTile(startPos ?? 0, tileLabel, preceding); + } + /** * Searches a string for the nearest marker in either direction to a given start position. * The search will include the start position, so markers at the start position are valid From 62fa7a940ab4c55a24292408befeeb4879fcb156 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:29:59 -0800 Subject: [PATCH 22/50] Remove deprecated load methods (#17903) ## Description `ContainerRuntime.load(...)` and `FluidDataStoreRuntime.load(...)` have been deprecated for some time. Getting rid of these methods will help with the effort to move to the `entryPoint` pattern. ## Breaking Changes ### Removed `ContainerRuntime.load(...)` The static method `ContainerRuntime.load(...)` has been removed. Please migrate all usage of this method to `ContainerRuntime.loadRuntime(...)`. ### Removed `FluidDataStoreRuntime.load(...)` The static method `FluidDataStoreRuntime.load(...)` has been removed. Please migrate all usage of this method to `FluidDataStoreRuntime` constructor. --- .changeset/fuzzy-cities-wonder.md | 7 ++++ .changeset/metal-geese-battle.md | 8 ++++ .../attributor/src/mixinAttributor.ts | 27 ------------- .../api-report/container-runtime.api.md | 2 - .../container-runtime/src/containerRuntime.ts | 40 ------------------- .../datastore/api-report/datastore.api.md | 2 - .../runtime/datastore/src/dataStoreRuntime.ts | 21 ---------- 7 files changed, 15 insertions(+), 92 deletions(-) create mode 100644 .changeset/fuzzy-cities-wonder.md create mode 100644 .changeset/metal-geese-battle.md 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/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/packages/framework/attributor/src/mixinAttributor.ts b/packages/framework/attributor/src/mixinAttributor.ts index e71cae2c7619..285edd78598a 100644 --- a/packages/framework/attributor/src/mixinAttributor.ts +++ b/packages/framework/attributor/src/mixinAttributor.ts @@ -103,33 +103,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; 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 f89b87ec78a5..14899af83171 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -169,8 +169,6 @@ export class ContainerRuntime extends TypedEventEmitter Promise, runtimeOptions?: IContainerRuntimeOptions, containerScope?: FluidObject, existing?: boolean, containerRuntimeCtor?: typeof ContainerRuntime): Promise; static loadRuntime(params: { context: IContainerContext; registryEntries: NamedFluidDataStoreRegistryEntries; diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 47b90a2ec4f5..a3ab4e45a1bf 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -736,46 +736,6 @@ export class ContainerRuntime 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: diff --git a/packages/runtime/datastore/api-report/datastore.api.md b/packages/runtime/datastore/api-report/datastore.api.md index 0b1e27fa9f6d..69154c65dea1 100644 --- a/packages/runtime/datastore/api-report/datastore.api.md +++ b/packages/runtime/datastore/api-report/datastore.api.md @@ -91,8 +91,6 @@ 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} */ From c53084d3ebca87c8881133e5337eddc9768a7fae Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:47:23 -0800 Subject: [PATCH 23/50] refactor(merge-tree sequence matrix): enable noImplicitAny (#18236) Enables noImplicitAny in merge-tree, sequence, and matrix. Note that it explicitly disables it for test code, though this is not necessary. The type-tests seem to break with this enabled for tests, and I haven't yet investigated why. For now it seems sane to skip the tests. --- .changeset/cold-mails-notice.md | 7 +++++++ packages/dds/matrix/api-report/matrix.api.md | 6 +++--- packages/dds/matrix/src/types.ts | 6 +++--- packages/dds/matrix/tsconfig.json | 1 + .../dds/merge-tree/api-report/merge-tree.api.md | 8 ++++---- .../dds/merge-tree/src/attributionCollection.ts | 10 +++++----- packages/dds/merge-tree/src/attributionPolicy.ts | 6 ++++-- packages/dds/merge-tree/src/client.ts | 6 +++--- packages/dds/merge-tree/src/mergeTree.ts | 9 ++++----- packages/dds/merge-tree/src/revertibles.ts | 6 +++--- packages/dds/merge-tree/src/test/tsconfig.json | 1 + packages/dds/merge-tree/tsconfig.json | 1 + packages/dds/sequence/api-report/sequence.api.md | 14 +++++++------- packages/dds/sequence/package.json | 1 + packages/dds/sequence/src/defaultMapInterfaces.ts | 4 ++-- packages/dds/sequence/src/intervalCollection.ts | 8 ++++---- packages/dds/sequence/src/sequence.ts | 8 ++++---- packages/dds/sequence/src/test/tsconfig.json | 1 + packages/dds/sequence/tsconfig.json | 1 + pnpm-lock.yaml | 2 ++ 20 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 .changeset/cold-mails-notice.md 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/packages/dds/matrix/api-report/matrix.api.md b/packages/dds/matrix/api-report/matrix.api.md index 2b3e581838a9..751d6ba45212 100644 --- a/packages/dds/matrix/api-report/matrix.api.md +++ b/packages/dds/matrix/api-report/matrix.api.md @@ -25,15 +25,15 @@ import { SummarySerializer } from '@fluidframework/shared-object-base'; // @public (undocumented) export interface IRevertible { // (undocumented) - discard(): any; + discard(): void; // (undocumented) - revert(): any; + revert(): void; } // @public (undocumented) export interface IUndoConsumer { // (undocumented) - pushToCurrentOperation(revertible: IRevertible): any; + pushToCurrentOperation(revertible: IRevertible): void; } // @public diff --git a/packages/dds/matrix/src/types.ts b/packages/dds/matrix/src/types.ts index 74a1aa5ff071..1233d4427bd6 100644 --- a/packages/dds/matrix/src/types.ts +++ b/packages/dds/matrix/src/types.ts @@ -10,13 +10,13 @@ * @public */ export interface IRevertible { - revert(); - discard(); + revert(): void; + discard(): void; } /** * @public */ 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 7dda517dbac3..758cdb4800b2 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -284,7 +284,7 @@ export interface IAttributionCollection { // @internal (undocumented) splitAt(pos: number): IAttributionCollection; // @internal - update(name: string | undefined, channel: IAttributionCollection): any; + update(name: string | undefined, channel: IAttributionCollection): void; } // @internal @sealed (undocumented) @@ -733,11 +733,11 @@ 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; } // @public (undocumented) diff --git a/packages/dds/merge-tree/src/attributionCollection.ts b/packages/dds/merge-tree/src/attributionCollection.ts index e53c424c5754..56928c6a0ff9 100644 --- a/packages/dds/merge-tree/src/attributionCollection.ts +++ b/packages/dds/merge-tree/src/attributionCollection.ts @@ -120,7 +120,7 @@ export interface IAttributionCollection { * @param channel - Updated collection for that channel. * @internal */ - update(name: string | undefined, channel: IAttributionCollection); + update(name: string | undefined, channel: IAttributionCollection): void; } // note: treats null and undefined as equivalent @@ -264,9 +264,9 @@ 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] }; } @@ -288,7 +288,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 2377c4482f3b..4e6bac3eac9a 100644 --- a/packages/dds/merge-tree/src/attributionPolicy.ts +++ b/packages/dds/merge-tree/src/attributionPolicy.ts @@ -46,8 +46,10 @@ function createAttributionPolicyFromCallbacks({ 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); diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index cc538ac1916e..ab032f202b40 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -88,7 +88,7 @@ export interface IIntegerRange { * @internal */ export interface IClientEvents { - (event: "normalize", listener: (target: IEventThisPlaceHolder) => void); + (event: "normalize", listener: (target: IEventThisPlaceHolder) => void): void; ( event: "delta", listener: ( @@ -96,7 +96,7 @@ export interface IClientEvents { deltaArgs: IMergeTreeDeltaCallbackArgs, target: IEventThisPlaceHolder, ) => void, - ); + ): void; ( event: "maintenance", listener: ( @@ -104,7 +104,7 @@ export interface IClientEvents { deltaArgs: IMergeTreeDeltaOpArgs | undefined, target: IEventThisPlaceHolder, ) => void, - ); + ): void; } /** diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 78673b8a1920..63f47b416c37 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -172,7 +172,7 @@ function tileShift( 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) { @@ -181,7 +181,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) { @@ -381,7 +381,7 @@ function getSlideToSegment( } const result: { seg?: ISegment } = {}; cache?.set(segment, result); - const goFurtherToFindSlideToSegment = (seg) => { + const goFurtherToFindSlideToSegment = (seg: ISegment) => { if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) { result.seg = seg; return false; @@ -618,7 +618,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 @@ -647,7 +647,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. diff --git a/packages/dds/merge-tree/src/revertibles.ts b/packages/dds/merge-tree/src/revertibles.ts index 4cf1907a3b40..854e28576e8a 100644 --- a/packages/dds/merge-tree/src/revertibles.ts +++ b/packages/dds/merge-tree/src/revertibles.ts @@ -63,9 +63,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; } /** 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/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/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index f401fea76dda..486403456f64 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -145,7 +145,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) @@ -170,9 +170,9 @@ export interface IIntervalCollection ex // @public 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; } // @public @sealed @deprecated (undocumented) @@ -364,11 +364,11 @@ 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; } // @public diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index 65f543efdb1c..858a34c0ec02 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -96,6 +96,7 @@ "@types/mocha": "^9.1.1", "@types/node": "^16.18.38", "@types/random-js": "^1.0.31", + "@types/uuid": "^9.0.2", "c8": "^7.7.1", "cross-env": "^7.0.3", "diff": "^3.5.0", diff --git a/packages/dds/sequence/src/defaultMapInterfaces.ts b/packages/dds/sequence/src/defaultMapInterfaces.ts index 6ffc65ee794e..d67aa670b2b6 100644 --- a/packages/dds/sequence/src/defaultMapInterfaces.ts +++ b/packages/dds/sequence/src/defaultMapInterfaces.ts @@ -127,7 +127,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 @@ -171,7 +171,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/intervalCollection.ts b/packages/dds/sequence/src/intervalCollection.ts index 9b9084f0320a..f940252d7090 100644 --- a/packages/dds/sequence/src/intervalCollection.ts +++ b/packages/dds/sequence/src/intervalCollection.ts @@ -688,7 +688,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. @@ -701,7 +701,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. @@ -719,7 +719,7 @@ export interface IIntervalCollectionEvent void, - ); + ): void; } // solely for type checking in the implementation of add - will be removed once @@ -867,7 +867,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/sequence.ts b/packages/dds/sequence/src/sequence.ts index e93563d655b0..6920e9c01fba 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -102,15 +102,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; } /** @@ -145,7 +145,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; } 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/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/pnpm-lock.yaml b/pnpm-lock.yaml index d3cee18d5765..dba93e687bc5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6822,6 +6822,7 @@ importers: '@types/mocha': ^9.1.1 '@types/node': ^16.18.38 '@types/random-js': ^1.0.31 + '@types/uuid': ^9.0.2 c8: ^7.7.1 cross-env: ^7.0.3 diff: ^3.5.0 @@ -6863,6 +6864,7 @@ importers: '@types/mocha': 9.1.1 '@types/node': 16.18.58 '@types/random-js': 1.0.31 + '@types/uuid': 9.0.5 c8: 7.14.0 cross-env: 7.0.3 diff: 3.5.0 From 1bc3dbc6d36d3c010c10d89454076658fcbb06e4 Mon Sep 17 00:00:00 2001 From: Taylor Williams <60717813+taylorsw04@users.noreply.github.com> Date: Mon, 13 Nov 2023 09:21:50 -0800 Subject: [PATCH 24/50] Cluster capacity request (#18262) ## Description This change annotates the compressor creation ranges with the desired cluster size. This allows for future "smart" request policies without needed to make a format change, as the policy can be run by one client without sequencing. ## Breaking Changes This breaks the IdCompressor persisted format, but no code yet uses it so this is acceptable. --- .../src/id-compressor/idCompressor.ts | 62 +++++++-------- .../id-compressor/idCompressor.perf.spec.ts | 24 ++++-- .../test/id-compressor/idCompressor.spec.ts | 74 ++++-------------- .../idCompressorTestUtilities.ts | 75 +++++++++---------- .../src/test/id-compressor/testCommon.ts | 17 ++--- .../api-report/runtime-definitions.api.md | 4 +- .../runtime/runtime-definitions/package.json | 11 +++ .../src/id-compressor/index.ts | 1 - .../id-compressor/persisted-types/0.0.1.ts | 20 +++-- .../id-compressor/persisted-types/index.ts | 1 - .../runtime/runtime-definitions/src/index.ts | 1 - ...ateRuntimeDefinitionsPrevious.generated.ts | 17 +---- .../src/test/idCompressor.spec.ts | 4 +- 13 files changed, 133 insertions(+), 178 deletions(-) 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/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/runtime-definitions/api-report/runtime-definitions.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md index b19cf2d7555a..e632bfbbc74f 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -171,6 +171,7 @@ export interface IdCreationRange { readonly ids?: { readonly firstGenCount: number; readonly count: number; + readonly requestedClusterSize: number; }; // (undocumented) readonly sessionId: SessionId; @@ -339,9 +340,6 @@ export type InboundAttachMessage = Omit & { snapshot: IAttachMessage["snapshot"] | null; }; -// @public -export const initialClusterCapacity = 512; - // @public (undocumented) export interface IProvideFluidDataStoreFactory { // (undocumented) diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index efee2f8d8232..dc011542c7f2 100644 --- a/packages/runtime/runtime-definitions/package.json +++ b/packages/runtime/runtime-definitions/package.json @@ -59,6 +59,17 @@ "RemovedTypeAliasDeclaration_IdCreationRangeWithStashedState": { "forwardCompat": false, "backCompat": false + }, + "InterfaceDeclaration_IdCreationRange": { + "forwardCompat": false + }, + "VariableDeclaration_initialClusterCapacity": { + "backCompat": false, + "forwardCompat": false + }, + "RemovedVariableDeclaration_initialClusterCapacity": { + "forwardCompat": false, + "backCompat": false } } } 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 35c0e39161fd..7dea2d13946f 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 @@ -34,13 +34,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. - */ -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/validateRuntimeDefinitionsPrevious.generated.ts b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts index 1299236a356d..f865dadb7441 100644 --- a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts @@ -1015,6 +1015,7 @@ declare function get_old_InterfaceDeclaration_IdCreationRange(): declare function use_current_InterfaceDeclaration_IdCreationRange( use: TypeOnly); use_current_InterfaceDeclaration_IdCreationRange( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_IdCreationRange()); /* @@ -1548,26 +1549,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); -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); -use_old_VariableDeclaration_initialClusterCapacity( - get_current_VariableDeclaration_initialClusterCapacity()); /* * Validate forward compat by using old type in place of current type 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 7b904a2d9e6c..da9a26ece251 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 @@ -720,8 +720,8 @@ describeNoCompat("IdCompressor Summaries", (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], }; } From a16a092b2569d220271d1e65bc01ad887fcdd69e Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Mon, 13 Nov 2023 10:52:32 -0800 Subject: [PATCH 25/50] Remove requestResolvedObjectFromContainer (#18265) 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. [AB#5453](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/5453) --- .changeset/tangy-geckos-swim.md | 7 ++ .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../api-report/container-loader.api.md | 4 -- packages/loader/container-loader/package.json | 7 +- packages/loader/container-loader/src/index.ts | 1 - .../loader/container-loader/src/loader.ts | 28 -------- ...lidateContainerLoaderPrevious.generated.ts | 16 +---- .../src/test/loaderTest.spec.ts | 66 +------------------ 8 files changed, 17 insertions(+), 114 deletions(-) create mode 100644 .changeset/tangy-geckos-swim.md 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 094cfcb10fa6..728305eb318d 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -114,6 +114,6 @@ const entryPoint = await container.getEntryPoint(); | `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 | | +| `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 | | 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 93dd9e08d07f..8d0bdd4a0137 100644 --- a/packages/loader/container-loader/api-report/container-loader.api.md +++ b/packages/loader/container-loader/api-report/container-loader.api.md @@ -22,7 +22,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'; @@ -136,9 +135,6 @@ export class Loader implements IHostLoader { // @public export type ProtocolHandlerBuilder = (attributes: IDocumentAttributes, snapshot: IQuorumSnapshot, sendProposal: (key: string, value: any) => number) => IProtocolHandler; -// @public @deprecated -export function requestResolvedObjectFromContainer(container: IContainer, headers?: IRequestHeader): Promise; - // @public 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 dced1594853c..5633bec62bab 100644 --- a/packages/loader/container-loader/package.json +++ b/packages/loader/container-loader/package.json @@ -105,6 +105,11 @@ "typescript": "~5.1.6" }, "typeValidation": { - "broken": {} + "broken": { + "RemovedFunctionDeclaration_requestResolvedObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + } + } } } 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 a3d1a25163b1..93e17256e6f3 100644 --- a/packages/loader/container-loader/src/loader.ts +++ b/packages/loader/container-loader/src/loader.ts @@ -20,7 +20,6 @@ import { // eslint-disable-next-line import/no-deprecated IFluidRouter, IRequest, - IRequestHeader, IResponse, } from "@fluidframework/core-interfaces"; import { @@ -269,33 +268,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 */ 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 7d69ad1b1387..5471c0403a55 100644 --- a/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts +++ b/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts @@ -336,26 +336,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); -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); -use_old_FunctionDeclaration_requestResolvedObjectFromContainer( - get_current_FunctionDeclaration_requestResolvedObjectFromContainer()); /* * Validate forward compat by using old type in place of current type 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 index 3ae46107a659..3e7b16160b46 100644 --- 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 @@ -16,11 +16,6 @@ import { } from "@fluid-private/test-version-utils"; import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { RuntimeHeaders } from "@fluidframework/container-runtime"; -import { - requestResolvedObjectFromContainer, - waitContainerToCatchUp, -} from "@fluidframework/container-loader"; -import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; // REVIEW: enable compat testing? describeNoCompat("Loader.request", (getTestObjectProvider, apis) => { @@ -135,8 +130,6 @@ describeNoCompat("Loader.request", (getTestObjectProvider, apis) => { [testFactoryWithRequestHeaders.type, Promise.resolve(testFactoryWithRequestHeaders)], ], requestHandlers: [innerRequestHandler], - // The requestResolvedObjectFromContainer expects the entryPoint to act as containerRuntime request router - provideEntryPoint: async (containerRuntime: IContainerRuntime) => containerRuntime, }); beforeEach(async () => { @@ -147,7 +140,7 @@ describeNoCompat("Loader.request", (getTestObjectProvider, apis) => { loader, provider.driver.createCreateNewRequest(provider.documentId), ); - dataStore1 = await requestFluidObject(container, "default"); + dataStore1 = (await container.getEntryPoint()) as TestSharedDataObject1; dataStore2 = await testSharedDataObjectFactory2.createInstance( dataStore1._context.containerRuntime, @@ -277,29 +270,6 @@ describeNoCompat("Loader.request", (getTestObjectProvider, apis) => { ); }); - 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", - ); - }); - it("can handle requests with headers", async () => { const containerUrl = await container.getAbsoluteUrl(""); assert(containerUrl, "url is undefined"); @@ -333,38 +303,4 @@ describeNoCompat("Loader.request", (getTestObjectProvider, apis) => { "Did not get the correct headers in the response", ); }); - - it("requestResolvedObjectFromContainer can handle requests with headers", async () => { - const dataStoreWithRequestHeaders = await testFactoryWithRequestHeaders.createInstance( - dataStore1._context.containerRuntime, - ); - dataStore1._root.set("key", dataStoreWithRequestHeaders.handle); - - // Flush all the ops - await provider.ensureSynchronized(); - - const url = await container.getAbsoluteUrl(dataStoreWithRequestHeaders.id); - assert(url, "Container should return absolute url"); - - const headers = { wait: false, [RuntimeHeaders.viaHandle]: true }; - // Request to the newly created data store with headers. - const newLoader = provider.createLoader([[provider.defaultCodeDetails, runtimeFactory]]); - const resolvedContainer = await newLoader.resolve({ url }); - await waitContainerToCatchUp(resolvedContainer); - await provider.ensureSynchronized(); - - const response = await requestResolvedObjectFromContainer(resolvedContainer, 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", - ); - }); }); From 1399d822f6db826ed39787e207884dad7a967765 Mon Sep 17 00:00:00 2001 From: Abram Sanderson Date: Tue, 21 Nov 2023 15:52:40 -0800 Subject: [PATCH 26/50] Use package.json main field to load legacy versions (#18433) ## Description Recent changes to FF package structure caused this brittle assumption to break PRs into next. This fixes the legacy package module loading logic to not assume the main entrypoint is under `dist/index.js`. Co-authored-by: Abram Sanderson --- packages/test/test-version-utils/src/versionUtils.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/test/test-version-utils/src/versionUtils.ts b/packages/test/test-version-utils/src/versionUtils.ts index d04128e29cf8..1ae721168dd9 100644 --- a/packages/test/test-version-utils/src/versionUtils.ts +++ b/packages/test/test-version-utils/src/versionUtils.ts @@ -271,8 +271,13 @@ export function checkInstalled(requested: string) { ); } -export const loadPackage = async (modulePath: string, pkg: string): Promise => - import(pathToFileURL(path.join(modulePath, "node_modules", pkg, "dist", "index.js")).href); +export const loadPackage = async (modulePath: string, pkg: string): Promise => { + const pkgPath = path.join(modulePath, "node_modules", pkg); + const pkgJson: { main: string } = JSON.parse( + readFileSync(path.join(pkgPath, "package.json"), { encoding: "utf8" }), + ); + return import(pathToFileURL(path.join(pkgPath, pkgJson.main)).href); +}; /** * From 13ba6e07e7c6b99616fa9a9810518c9cffaf33bd Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:32:24 -0800 Subject: [PATCH 27/50] feat(merge-tree): initial implementation of obliterate (#12578) Implements the [obliterate op](https://github.com/microsoft/FluidFramework/pull/11608/files). --- .changeset/eager-teeth-accept.md | 11 + .../merge-tree/api-report/merge-tree.api.md | 28 +- packages/dds/merge-tree/src/client.ts | 82 ++- packages/dds/merge-tree/src/index.ts | 3 + packages/dds/merge-tree/src/mergeTree.ts | 486 +++++++++++++++++- packages/dds/merge-tree/src/mergeTreeNodes.ts | 31 +- packages/dds/merge-tree/src/partialLengths.ts | 483 ++++++++++++++--- packages/dds/merge-tree/src/snapshotChunks.ts | 3 + packages/dds/merge-tree/src/snapshotLoader.ts | 11 + packages/dds/merge-tree/src/snapshotV1.ts | 33 +- .../src/test/mergeTreeOperationRunner.ts | 6 + .../src/test/obliterate.concurrent.spec.ts | 2 +- .../src/test/obliterate.partialLength.spec.ts | 2 +- .../src/test/obliterate.reconnect.spec.ts | 2 +- .../merge-tree/src/test/obliterate.spec.ts | 2 +- .../dds/merge-tree/src/test/snapshot.spec.ts | 43 +- .../dds/merge-tree/src/test/snapshot.utils.ts | 4 + .../dds/merge-tree/src/test/testClient.ts | 7 +- packages/dds/merge-tree/src/test/testUtils.ts | 10 +- .../dds/sequence/api-report/sequence.api.md | 2 + packages/dds/sequence/package.json | 4 + packages/dds/sequence/src/sequence.ts | 31 ++ .../dds/sequence/src/test/fuzz/fuzzUtils.ts | 23 +- .../test/fuzz/intervalCollection.fuzz.spec.ts | 2 + .../fuzz/intervalRevertibles.fuzz.spec.ts | 2 + .../src/test/intervalRebasing.spec.ts | 139 +++++ .../validateSequencePrevious.generated.ts | 4 + packages/framework/undo-redo/package.json | 1 + .../validateUndoRedoPrevious.generated.ts | 1 + pnpm-lock.yaml | 10 +- 30 files changed, 1349 insertions(+), 119 deletions(-) create mode 100644 .changeset/eager-teeth-accept.md 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/packages/dds/merge-tree/api-report/merge-tree.api.md b/packages/dds/merge-tree/api-report/merge-tree.api.md index 758cdb4800b2..2878dd00c8a4 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -236,6 +236,9 @@ export function createInsertSegmentOp(pos: number, segment: ISegment): IMergeTre // @internal (undocumented) export function createMap(): MapLike; +// @internal +export function createObliterateRangeOp(start: number, end: number): IMergeTreeObliterateMsg; + // @internal export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemoveMsg; @@ -447,6 +450,18 @@ export interface IMergeTreeInsertMsg extends IMergeTreeDelta { export interface IMergeTreeMaintenanceCallbackArgs extends IMergeTreeDeltaCallbackArgs { } +// @public (undocumented) +export interface IMergeTreeObliterateMsg extends IMergeTreeDelta { + // (undocumented) + pos1?: number; + // (undocumented) + pos2?: number; + relativePos1?: never; + relativePos2?: never; + // (undocumented) + type: typeof MergeTreeDeltaType.OBLITERATE; +} + // @public (undocumented) export type IMergeTreeOp = IMergeTreeDeltaOp | IMergeTreeGroupMsg; @@ -455,6 +470,7 @@ export interface IMergeTreeOptions { attribution?: IMergeTreeAttributionOptions; // (undocumented) catchUpBlobName?: string; + mergeTreeEnableObliterate?: boolean; // @alpha mergeTreeReferencesCanSlideToEndpoint?: boolean; // (undocumented) @@ -490,6 +506,16 @@ export interface IMergeTreeTextHelper { getText(refSeq: number, clientId: number, placeholder: string, start?: number, end?: number): string; } +// @public +export interface IMoveInfo { + localMovedSeq?: number; + movedClientIds: number[]; + movedSeq: number; + movedSeqs: number[]; + moveDst?: ReferencePosition; + wasMovedOnInsert: boolean; +} + // @internal (undocumented) export interface IRBAugmentation { // (undocumented) @@ -932,7 +958,7 @@ export interface SegmentGroup { // (undocumented) refSeq: number; // (undocumented) - segments: ISegment[]; + segments: ISegmentLeaf[]; } // @public (undocumented) diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index ab032f202b40..30071923236e 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -23,16 +23,13 @@ import { CollaborationWindow, compareStrings, IConsensusInfo, + IMoveInfo, IMergeLeaf, ISegment, ISegmentAction, Marker, SegmentGroup, } from "./mergeTreeNodes"; -import { - IMergeTreeDeltaCallbackArgs, - IMergeTreeMaintenanceCallbackArgs, -} from "./mergeTreeDeltaCallback"; import { createAnnotateMarkerOp, createAnnotateRangeOp, @@ -66,11 +63,24 @@ import { ReferencePosition, DetachedReferencePosition } from "./referencePositio 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 @@ -760,6 +770,13 @@ export class Client extends TypedEventEmitter { this.pendingRebase = undefined; } + // if this is an obliterate op, keep all segments in same segment group + 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 @@ -786,13 +803,16 @@ 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, @@ -813,13 +833,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, @@ -827,23 +853,55 @@ 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) { 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; } diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index 00408d33f551..29994acc4c6b 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -56,6 +56,7 @@ export { debugMarkerToString, IJSONMarkerSegment, IMergeNodeCommon, + IMoveInfo, IRemovalInfo, ISegment, ISegmentAction, @@ -78,6 +79,7 @@ export { createInsertOp, createInsertSegmentOp, createRemoveRangeOp, + createObliterateRangeOp, } from "./opBuilder"; export { ICombiningOp, @@ -93,6 +95,7 @@ export { IRelativePosition, MergeTreeDeltaType, ReferenceType, + IMergeTreeObliterateMsg, } from "./ops"; export { addProperties, createMap, MapLike, matchProperties, PropertySet } from "./properties"; export { diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 63f47b416c37..ba6530a0d741 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -30,6 +30,7 @@ import { IMergeBlock, IMergeLeaf, IMergeNode, + IMoveInfo, InsertContext, IRemovalInfo, ISegment, @@ -42,6 +43,7 @@ import { reservedMarkerIdKey, SegmentActions, SegmentGroup, + toMoveInfo, seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; @@ -98,6 +100,26 @@ const minListenerComparer: Comparer = { 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; } @@ -107,6 +129,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; @@ -270,6 +301,16 @@ 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; } export interface IMergeTreeAttributionOptions { @@ -371,7 +412,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]; } @@ -382,10 +427,11 @@ function getSlideToSegment( const result: { seg?: ISegment } = {}; cache?.set(segment, result); const goFurtherToFindSlideToSegment = (seg: ISegment) => { - if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAcked(seg)) { + if (seg.seq !== UnassignedSequenceNumber && !isRemovedAndAckedOrMovedAndAcked(seg)) { result.seg = seg; return false; } + // TODO: ADO#3715 moveSeq should be taken into account here if (cache !== undefined && seg.removedSeq === segment.removedSeq) { cache.set(seg, result); } @@ -494,6 +540,38 @@ export class MergeTree { 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 + */ + private readonly locallyMovedSegments: Map = new Map(); + public constructor(public options?: IMergeTreeOptions) { this._root = this.makeBlock(0); this._root.mergeTree = this; @@ -552,9 +630,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 @@ -568,7 +650,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 ( @@ -576,7 +658,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; } @@ -589,7 +675,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; } @@ -903,7 +990,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) { @@ -1032,6 +1119,8 @@ export class MergeTree { } else { const segment = node; const removalInfo = toRemovalInfo(segment); + const moveInfo = toMoveInfo(segment); + if (removalInfo !== undefined) { if (seqLTE(removalInfo.removedSeq, this.collabWindow.minSeq)) { return undefined; @@ -1044,6 +1133,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; @@ -1082,6 +1183,8 @@ 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); } @@ -1102,7 +1205,7 @@ 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; @@ -1358,8 +1461,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); @@ -1374,7 +1512,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); } @@ -1624,6 +1765,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); + } + } } } } @@ -1675,12 +1939,25 @@ export class MergeTree { 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; + + 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, @@ -1915,9 +2192,126 @@ export class MergeTree { overwrite: boolean = false, opArgs: IMergeTreeDeltaOpArgs, ): void { - throw new UsageError( - "Attempted to use obliterate. Obliterate is not currently implemented.", + 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); + } + 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( @@ -1939,6 +2333,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) { @@ -2175,7 +2570,7 @@ export class MergeTree { if ( _segment !== "start" && _segment !== "end" && - isRemovedAndAcked(_segment) && + isRemovedAndAckedOrMovedAndAcked(_segment) && !refTypeIncludesFlag(refType, ReferenceType.SlideOnRemove | ReferenceType.Transient) && _segment.endpointType === undefined ) { @@ -2426,6 +2821,13 @@ export class MergeTree { } } + /** + * 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, @@ -2434,6 +2836,7 @@ export class MergeTree { start?: number, end?: number, splitRange: boolean = false, + visibilitySeq: number = refSeq, ) { if (splitRange) { if (start) { @@ -2443,9 +2846,42 @@ export class MergeTree { this.ensureIntervalBoundary(end, refSeq, clientId); } } - this.nodeMap(refSeq, clientId, handler, accum, undefined, start, end); + 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, @@ -2455,6 +2891,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) { @@ -2470,12 +2907,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/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index bbdcfdcd7a32..d2077737b4e1 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -10,6 +10,7 @@ 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"; @@ -182,6 +183,19 @@ export interface IMoveInfo { 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. @@ -363,7 +377,7 @@ export interface SegmentActions { * @internal */ export interface SegmentGroup { - segments: ISegment[]; + segments: ISegmentLeaf[]; previousProps?: PropertySet[]; localSeq?: number; refSeq: number; @@ -547,6 +561,21 @@ 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`); } diff --git a/packages/dds/merge-tree/src/partialLengths.ts b/packages/dds/merge-tree/src/partialLengths.ts index dadd88677f80..b5becd0ae1c5 100644 --- a/packages/dds/merge-tree/src/partialLengths.ts +++ b/packages/dds/merge-tree/src/partialLengths.ts @@ -6,18 +6,20 @@ import { assert } from "@fluidframework/core-utils"; import { Property, RedBlackTree } from "./collections"; import { UnassignedSequenceNumber } from "./constants"; +import { MergeTree } from "./mergeTree"; import { CollaborationWindow, compareNumbers, IMergeBlock, IMergeNode, + IMoveInfo, IRemovalInfo, ISegment, + toMoveInfo, seqLTE, toRemovalInfo, } from "./mergeTreeNodes"; import { SortedSet } from "./sortedSet"; -import { MergeTree } from "./mergeTree"; class PartialSequenceLengthsSet extends SortedSet { protected getKey(item: PartialSequenceLength): number { @@ -47,6 +49,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 +131,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 +157,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 { @@ -373,16 +391,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, ); } } @@ -441,38 +463,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; + + // 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 (removalInfo) { + 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 @@ -483,34 +705,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; @@ -553,6 +804,7 @@ export class PartialSequenceLengths { partialLengths: PartialSequenceLengthsSet, seq: number, seqSeglen: number, + remoteObliteratedLen?: number, clientId?: number, ) { let seqPartialLen: PartialSequenceLength | undefined; @@ -576,9 +828,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 @@ -649,6 +903,7 @@ export class PartialSequenceLengths { 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++) { @@ -659,22 +914,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; } } @@ -684,9 +981,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); } @@ -835,8 +1144,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 @@ -846,6 +1156,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 { @@ -924,6 +1243,15 @@ function verifyPartialLengths( // (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; } @@ -1010,27 +1338,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; } } @@ -1056,6 +1402,9 @@ function mergePartialLengths( mergedLengths.addOrUpdate({ ...partialLength, overlapRemoveClients: cloneOverlapRemoveClients(partialLength.overlapRemoveClients), + overlapObliterateClients: cloneOverlapRemoveClients( + partialLength.overlapObliterateClients, + ), }); } return mergedLengths; 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 90ae9a43359c..5561063b7090 100644 --- a/packages/dds/merge-tree/src/snapshotLoader.ts +++ b/packages/dds/merge-tree/src/snapshotLoader.ts @@ -106,6 +106,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 @@ -121,6 +127,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 fab23c9263e8..bdace7227630 100644 --- a/packages/dds/merge-tree/src/snapshotV1.ts +++ b/packages/dds/merge-tree/src/snapshotV1.ts @@ -216,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; } @@ -228,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). @@ -292,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/test/mergeTreeOperationRunner.ts b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts index a04fa2b3c091..5d72bc6335f5 100644 --- a/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts +++ b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts @@ -26,6 +26,12 @@ 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); diff --git a/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts b/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts index 998143338ac3..43ace9a16b2c 100644 --- a/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.concurrent.spec.ts @@ -40,7 +40,7 @@ import { ReconnectTestHelper } from "./reconnectHelper"; */ for (const incremental of [true, false]) { - describe.skip(`obliterate partial lengths incremental = ${incremental}`, () => { + describe(`obliterate partial lengths incremental = ${incremental}`, () => { beforeEach(() => { PartialSequenceLengths.options.verifier = verify; PartialSequenceLengths.options.verifyExpected = verifyExpected; 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 9b74109c48c4..9df0a6984acd 100644 --- a/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.partialLength.spec.ts @@ -9,7 +9,7 @@ import { PartialSequenceLengths, verify, verifyExpected } from "../partialLength import { TestClient } from "./testClient"; import { insertText, validatePartialLengths } from "./testUtils"; -describe.skip("obliterate partial lengths", () => { +describe("obliterate partial lengths", () => { let client: TestClient; let refSeq: number; const localClientId = 17; 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 fc776262e75f..a44f8734d734 100644 --- a/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.reconnect.spec.ts @@ -9,7 +9,7 @@ import { MergeTree } from "../mergeTree"; import { ReconnectTestHelper } from "./reconnectHelper"; for (const incremental of [true, false]) { - describe.skip(`obliterate partial lengths incremental = ${incremental}`, () => { + describe(`obliterate partial lengths incremental = ${incremental}`, () => { beforeEach(() => { PartialSequenceLengths.options.verifier = verify; PartialSequenceLengths.options.verifyExpected = verifyExpected; diff --git a/packages/dds/merge-tree/src/test/obliterate.spec.ts b/packages/dds/merge-tree/src/test/obliterate.spec.ts index 6a8a52e1fdb1..68ea6577459a 100644 --- a/packages/dds/merge-tree/src/test/obliterate.spec.ts +++ b/packages/dds/merge-tree/src/test/obliterate.spec.ts @@ -8,7 +8,7 @@ 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; 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..92123caa68a6 100644 --- a/packages/dds/merge-tree/src/test/snapshot.utils.ts +++ b/packages/dds/merge-tree/src/test/snapshot.utils.ts @@ -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/testClient.ts b/packages/dds/merge-tree/src/test/testClient.ts index a524f7423c05..7f81e2e52cbe 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -424,7 +424,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, @@ -441,7 +444,7 @@ 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; } diff --git a/packages/dds/merge-tree/src/test/testUtils.ts b/packages/dds/merge-tree/src/test/testUtils.ts index 1fedf8584719..09d06a50dc47 100644 --- a/packages/dds/merge-tree/src/test/testUtils.ts +++ b/packages/dds/merge-tree/src/test/testUtils.ts @@ -195,8 +195,16 @@ function getPartialLengths( segment.localRemovedSeq <= localSeq) || (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; diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 486403456f64..13877162c778 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -577,6 +577,8 @@ export abstract class SharedSegmentSequence extends SharedOb get loaded(): Promise; protected loadedDeferred: Deferred; localReferencePositionToPosition(lref: ReferencePosition): number; + // @alpha + obliterateRange(start: number, end: number): void; // (undocumented) protected onConnect(): void; // (undocumented) diff --git a/packages/dds/sequence/package.json b/packages/dds/sequence/package.json index b8df716d4466..655d6dff22a3 100644 --- a/packages/dds/sequence/package.json +++ b/packages/dds/sequence/package.json @@ -121,15 +121,19 @@ "typeValidation": { "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": { diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 6920e9c01fba..5888787bd5c7 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -37,6 +37,8 @@ import { ReferenceType, MergeTreeRevertibleDriver, SegmentGroup, + IMergeTreeObliterateMsg, + createObliterateRangeOp, SlidingPreference, } from "@fluidframework/merge-tree"; import { ObjectStoragePartition, SummaryTreeBuilder } from "@fluidframework/runtime-utils"; @@ -188,6 +190,22 @@ 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: } } @@ -269,6 +287,19 @@ export abstract class SharedSegmentSequence this.guardReentrancy(() => this.client.removeRangeLocal(start, end)); } + /** + * 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 + * + * @alpha + */ + 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 diff --git a/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts b/packages/dds/sequence/src/test/fuzz/fuzzUtils.ts index 6accbba06f62..2de2210a46a8 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 4f583a56b6df..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)], 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/intervalRebasing.spec.ts b/packages/dds/sequence/src/test/intervalRebasing.spec.ts index d41e97eea355..d993d7553a01 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/types/validateSequencePrevious.generated.ts b/packages/dds/sequence/src/test/types/validateSequencePrevious.generated.ts index c0ff446fe1f3..b874bf927263 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); use_current_InterfaceDeclaration_ISharedString( + // @ts-expect-error compatibility expected to be broken get_old_InterfaceDeclaration_ISharedString()); /* @@ -944,6 +945,7 @@ declare function get_old_ClassDeclaration_SharedSegmentSequence(): declare function use_current_ClassDeclaration_SharedSegmentSequence( use: TypeOnly>); use_current_ClassDeclaration_SharedSegmentSequence( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSegmentSequence()); /* @@ -969,6 +971,7 @@ declare function get_old_ClassDeclaration_SharedSequence(): declare function use_current_ClassDeclaration_SharedSequence( use: TypeOnly>); use_current_ClassDeclaration_SharedSequence( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSequence()); /* @@ -994,6 +997,7 @@ declare function get_old_ClassDeclaration_SharedString(): declare function use_current_ClassDeclaration_SharedString( use: TypeOnly); use_current_ClassDeclaration_SharedString( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedString()); /* diff --git a/packages/framework/undo-redo/package.json b/packages/framework/undo-redo/package.json index aeb4597c70f0..ae2214a5613e 100644 --- a/packages/framework/undo-redo/package.json +++ b/packages/framework/undo-redo/package.json @@ -93,6 +93,7 @@ "typeValidation": { "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 bbca20d37d6c..d6555cd63f2a 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); use_current_ClassDeclaration_SharedSegmentSequenceRevertible( + // @ts-expect-error compatibility expected to be broken get_old_ClassDeclaration_SharedSegmentSequenceRevertible()); /* diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c20868cc77bd..4da0c6a336a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22864,7 +22864,7 @@ packages: webpack-cli: 4.x.x dependencies: webpack: 5.88.2_webpack-cli@4.10.0 - webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu + webpack-cli: 4.10.0_webpack@5.88.2 /@webpack-cli/info/1.5.0_webpack-cli@4.10.0: resolution: {integrity: sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==} @@ -22872,7 +22872,7 @@ packages: webpack-cli: 4.x.x dependencies: envinfo: 7.10.0 - webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu + webpack-cli: 4.10.0_webpack@5.88.2 /@webpack-cli/serve/1.7.0_hxwqhfj3xkkgp5omnyg2m7pnsm: resolution: {integrity: sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==} @@ -22895,8 +22895,7 @@ packages: webpack-dev-server: optional: true dependencies: - webpack-cli: 4.10.0_xrcblan7buurh2bchxgxle5xda - dev: true + webpack-cli: 4.10.0_webpack@5.88.2 /@wojtekmaj/enzyme-adapter-react-17/0.6.7_7ltvq4e2railvf5uya4ffxpe2a: resolution: {integrity: sha512-B+byiwi/T1bx5hcj9wc0fUL5Hlb5giSXJzcnEfJVl2j6dGV2NJfcxDBYX0WWwIxlzNiFz8kAvlkFWI2y/nscZQ==} @@ -44049,7 +44048,6 @@ packages: rechoir: 0.7.1 webpack: 5.88.2_webpack-cli@4.10.0 webpack-merge: 5.9.0 - dev: true /webpack-cli/4.10.0_xrcblan7buurh2bchxgxle5xda: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} @@ -44433,7 +44431,7 @@ packages: tapable: 2.2.1 terser-webpack-plugin: 5.3.9_webpack@5.88.2 watchpack: 2.4.0 - webpack-cli: 4.10.0_ai6cu5vnuisb2akyozxbiaqwvu + webpack-cli: 4.10.0_webpack@5.88.2 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' From 37ba6991a50aa619809a6f42ed91b076c7dc0d60 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Wed, 22 Nov 2023 03:04:25 -0800 Subject: [PATCH 28/50] refactor(merge-tree): misc cleanup (#18317) Miscellaneous merge-tree cleanup, though larger scale than some previous PRs. 1. Replaces the merge-tree heap implementation with the shared implementation in core-utils 2. Removes merge-tree clone functionality. This was largely unused. 3. Inverts conditions in splitAt to reduce nesting 4. Resolves `mapIdToSegment` todo to remove segment id when the segment is removed 5. Adds docs to most merge-tree types that I intend to re-export through sequence Potentially controversially: special cases marker segments. The only place we use the `idToSegment` property is during marker lookup. --- .../core-utils/api-report/core-utils.api.md | 4 +- packages/common/core-utils/src/heap.ts | 8 +- .../merge-tree/api-report/merge-tree.api.md | 19 ++- .../dds/merge-tree/src/collections/heap.ts | 70 ----------- .../dds/merge-tree/src/collections/index.ts | 1 - packages/dds/merge-tree/src/mergeTree.ts | 115 +++++++----------- packages/dds/merge-tree/src/mergeTreeNodes.ts | 104 ++++++++++------ .../dds/merge-tree/src/mergeTreeTracking.ts | 3 + packages/dds/merge-tree/src/ops.ts | 15 ++- packages/dds/merge-tree/src/properties.ts | 17 ++- .../dds/merge-tree/src/referencePositions.ts | 7 +- .../dds/merge-tree/src/test/testServer.ts | 18 +-- .../dds/merge-tree/src/test/wordUnitTests.ts | 18 +-- packages/dds/merge-tree/src/textSegment.ts | 3 +- packages/dds/merge-tree/src/zamboni.ts | 9 +- 15 files changed, 183 insertions(+), 228 deletions(-) delete mode 100644 packages/dds/merge-tree/src/collections/heap.ts 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 b241e1586d8f..d5006e95efe9 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/src/heap.ts b/packages/common/core-utils/src/heap.ts index a7e9827b5f6a..f0706ccd5e6e 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/dds/merge-tree/api-report/merge-tree.api.md b/packages/dds/merge-tree/api-report/merge-tree.api.md index 2878dd00c8a4..c2445735b6ce 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'; @@ -242,7 +243,7 @@ export function createObliterateRangeOp(start: number, end: number): IMergeTreeO // @internal export function createRemoveRangeOp(start: number, end: number): IMergeTreeRemoveMsg; -// @public (undocumented) +// @public export function debugMarkerToString(marker: Marker): string; // @public (undocumented) @@ -548,7 +549,6 @@ export interface IRemovalInfo { export interface ISegment extends IMergeNodeCommon, Partial, Partial { // @internal ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; - // (undocumented) addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; // (undocumented) append(segment: ISegment): void; @@ -653,14 +653,14 @@ export interface LocalReferencePosition extends ReferencePosition { readonly trackingCollection: TrackingGroupCollection; } -// @public (undocumented) +// @public export interface MapLike { // (undocumented) [index: string]: T; } -// @public (undocumented) -export class Marker extends BaseSegment implements ReferencePosition { +// @public +export class Marker extends BaseSegment implements ReferencePosition, ISegment { constructor(refType: ReferenceType); // (undocumented) append(): void; @@ -805,7 +805,7 @@ export interface PropertyAction { (p: Property, accum?: TAccum): boolean; } -// @public (undocumented) +// @public export type PropertySet = MapLike; // @internal (undocumented) @@ -901,7 +901,6 @@ export interface ReferencePosition { getSegment(): ISegment | undefined; // (undocumented) isLeaf(): this is ISegment; - // (undocumented) properties?: PropertySet; // (undocumented) refType: ReferenceType; @@ -910,9 +909,7 @@ export interface ReferencePosition { // @public export enum ReferenceType { - // (undocumented) RangeBegin = 16, - // (undocumented) RangeEnd = 32, // (undocumented) Simple = 0, @@ -934,7 +931,7 @@ export function refHasTileLabels(refPos: ReferencePosition): boolean; // @public (undocumented) export function refTypeIncludesFlag(refPosOrType: ReferencePosition | ReferenceType, flags: ReferenceType): boolean; -// @public (undocumented) +// @public export const reservedMarkerIdKey = "markerId"; // @public (undocumented) @@ -1103,7 +1100,7 @@ export class TrackingGroup implements ITrackingGroup { unlink(trackable: Trackable): boolean; } -// @public (undocumented) +// @public export class TrackingGroupCollection { constructor(trackable: Trackable); // (undocumented) 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 200c09ebc981..64235c8c0a7b 100644 --- a/packages/dds/merge-tree/src/collections/index.ts +++ b/packages/dds/merge-tree/src/collections/index.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -export { Comparer, Heap } from "./heap"; export { DoublyLinkedList, ListNode, ListNodeRange, walkList } from "./list"; export { ConflictAction, diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index ba6530a0d741..a36b26fa0f71 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -6,10 +6,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* 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, DoublyLinkedList, ListNode } from "./collections"; +import { DoublyLinkedList, ListNode } from "./collections"; import { NonCollabClient, TreeMaintenanceSequenceNumber, @@ -90,7 +90,7 @@ import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment"; */ export type ISegmentLeaf = ISegment & IMergeLeaf; -const minListenerComparer: Comparer = { +const minListenerComparer: IComparer = { min: { minRequired: Number.MIN_VALUE, onMinGE: () => { @@ -145,7 +145,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, }; @@ -223,33 +223,6 @@ function addTileIfNotPresent(tile: ReferencePosition, tiles: MapLike, - leftmostTiles: MapLike, -) { - if (node.isLeaf()) { - const segment = node; - if ((mergeTree.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) { - mergeTree.mapIdToSegment(markerId, segment); - } - if (refTypeIncludesFlag(segment, ReferenceType.Tile)) { - addTile(segment, rightmostTiles); - addTileIfNotPresent(segment, leftmostTiles); - } - } - } else { - const block = node as IHierBlock; - extend(rightmostTiles, block.rightmostTiles); - extendIfUndefined(leftmostTiles, block.leftmostTiles); - } -} - class HierMergeBlock extends MergeBlock implements IHierBlock { public rightmostTiles: MapLike; public leftmostTiles: MapLike; @@ -522,7 +495,7 @@ export class MergeTree { public readonly pendingSegments = new DoublyLinkedList(); - public readonly segmentsToScour = new Heap([], LRUSegmentComparer); + public readonly segmentsToScour = new Heap(LRUSegmentComparer); public readonly attributionPolicy: AttributionPolicy | undefined; @@ -532,10 +505,9 @@ 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 readonly idToMarker = new Map(); private minSeqListeners: Heap | undefined; public mergeTreeDeltaCallback?: MergeTreeDeltaCallback; public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback; @@ -594,29 +566,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 @@ -684,9 +633,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) { @@ -767,10 +718,6 @@ export class MergeTree { } } - public getCollabWindow() { - return this.collabWindow; - } - public getLength(refSeq: number, clientId: number): number { return this.blockLength(this.root, refSeq, clientId); } @@ -1153,7 +1100,7 @@ export class MergeTree { } public addMinSeqListener(minRequired: number, onMinGE: (minSeq: number) => void) { - this.minSeqListeners ??= new Heap([], minListenerComparer); + this.minSeqListeners ??= new Heap(minListenerComparer); this.minSeqListeners.add({ minRequired, onMinGE }); } @@ -1161,7 +1108,7 @@ export class MergeTree { if (this.minSeqListeners) { while ( this.minSeqListeners.count() > 0 && - this.minSeqListeners.peek().minRequired <= this.collabWindow.minSeq + this.minSeqListeners.peek()!.value.minRequired <= this.collabWindow.minSeq ) { const minListener = this.minSeqListeners.get()!; minListener.onMinGE(this.collabWindow.minSeq); @@ -1570,8 +1517,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); } /** @@ -1589,7 +1536,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); @@ -1740,7 +1687,7 @@ export class MergeTree { if (Marker.is(newSegment)) { const markerId = newSegment.getId(); if (markerId) { - this.mapIdToSegment(markerId, newSegment); + this.idToMarker.set(markerId, newSegment); } } @@ -2760,6 +2707,32 @@ 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; + extend(rightmostTiles, block.rightmostTiles); + extendIfUndefined(leftmostTiles, block.leftmostTiles); + } + } + private blockUpdate(block: IMergeBlock) { let len: number | undefined; const hierBlock = block.hierBlock(); @@ -2775,7 +2748,7 @@ export class MergeTree { len += nodeLength; } if (hierBlock) { - addNodeReferences(this, child, hierBlock.rightmostTiles, hierBlock.leftmostTiles); + this.addNodeReferences(child, hierBlock.rightmostTiles, hierBlock.leftmostTiles); } } diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index d2077737b4e1..9ef86db778f9 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -273,6 +273,13 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti * 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, @@ -582,41 +589,47 @@ export abstract class BaseSegment extends MergeNode implements ISegment { } 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; - 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; + 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) { @@ -658,6 +671,12 @@ export abstract class BaseSegment extends MergeNode implements ISegment { protected abstract createSplitSegmentAt(pos: number): BaseSegment | undefined; } +/** + * 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. + */ export const reservedMarkerIdKey = "markerId"; export const reservedMarkerSimpleTypeKey = "markerSimpleType"; @@ -665,7 +684,16 @@ export interface IJSONMarkerSegment extends IJSONSegment { marker: IMarkerDef; } -export class Marker extends BaseSegment implements ReferencePosition { +/** + * 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. + */ +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; @@ -776,6 +804,12 @@ export interface MinListener { 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. + */ export function debugMarkerToString(marker: Marker): string { let bbuf = ""; if (refTypeIncludesFlag(marker, ReferenceType.Tile)) { diff --git a/packages/dds/merge-tree/src/mergeTreeTracking.ts b/packages/dds/merge-tree/src/mergeTreeTracking.ts index 8109d0692851..58149af9c473 100644 --- a/packages/dds/merge-tree/src/mergeTreeTracking.ts +++ b/packages/dds/merge-tree/src/mergeTreeTracking.ts @@ -90,6 +90,9 @@ export class UnorderedTrackingGroup implements ITrackingGroup { } } +/** + * A collection of {@link ITrackingGroup}. + */ export class TrackingGroupCollection { private readonly _trackingGroups: Set; diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index ff8fe4299885..390eb59f1629 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} */ export enum ReferenceType { Simple = 0x0, @@ -12,8 +12,21 @@ export enum ReferenceType { * Allows this reference to be located using the `findTile` API on merge-tree. */ Tile = 0x1, + + /** + * Denotes that this reference begins the start of an interval. This is + * generally not meaningful outside the context of interval collections + * on SharedString. + */ RangeBegin = 0x10, + + /** + * Denotes that this reference is the end of an interval. This is + * generally not meaningful outside the context of interval collections + * on SharedString. + */ RangeEnd = 0x20, + /** * When a segment is marked removed (locally or with ack), this reference will slide to the first * valid option of: diff --git a/packages/dds/merge-tree/src/properties.ts b/packages/dds/merge-tree/src/properties.ts index 64641d0868cb..74139b0c695c 100644 --- a/packages/dds/merge-tree/src/properties.ts +++ b/packages/dds/merge-tree/src/properties.ts @@ -5,17 +5,24 @@ import { ICombiningOp } from "./ops"; +/** + * Any mapping from a string to values of type `T` + */ 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 + */ export type PropertySet = MapLike; -// Assume these are created with Object.create(null) - export interface IConsensusValue { seq: number; value: any; diff --git a/packages/dds/merge-tree/src/referencePositions.ts b/packages/dds/merge-tree/src/referencePositions.ts index aa490a0a24a1..cbecd98262f0 100644 --- a/packages/dds/merge-tree/src/referencePositions.ts +++ b/packages/dds/merge-tree/src/referencePositions.ts @@ -41,12 +41,15 @@ export function refHasTileLabels(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; diff --git a/packages/dds/merge-tree/src/test/testServer.ts b/packages/dds/merge-tree/src/test/testServer.ts index 8c7fe0d9562f..2663bc52cfec 100644 --- a/packages/dds/merge-tree/src/test/testServer.ts +++ b/packages/dds/merge-tree/src/test/testServer.ts @@ -4,7 +4,8 @@ */ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { Comparer, Heap, RedBlackTree } from "../collections"; +import { Heap, IComparer } from "@fluidframework/core-utils"; +import { RedBlackTree } from "../collections"; import { compareNumbers } from "../mergeTreeNodes"; import { PropertySet } from "../properties"; import { MergeTreeTextHelper } from "../MergeTreeTextHelper"; @@ -15,7 +16,7 @@ interface ClientSeq { clientId: string; } -const clientSeqComparer: Comparer = { +const clientSeqComparer: IComparer = { min: { refSeq: -1, clientId: "" }, compare: (a, b) => a.refSeq - b.refSeq, }; @@ -27,14 +28,14 @@ const clientSeqComparer: Comparer = { export class TestServer extends TestClient { seq = 1; clients: TestClient[] = []; - clientSeqNumbers: Heap = new Heap([], clientSeqComparer); + clientSeqNumbers: Heap = new Heap(clientSeqComparer); upstreamMap: RedBlackTree = new RedBlackTree(compareNumbers); constructor(options?: PropertySet) { super(options); } addClients(clients: TestClient[]) { - this.clientSeqNumbers = new Heap([], clientSeqComparer); + this.clientSeqNumbers = new Heap(clientSeqComparer); this.clients = clients; for (const client of clients) { this.clientSeqNumbers.add({ @@ -98,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; } 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 6b7a6afc2d9d..6bdeb764707f 100644 --- a/packages/dds/merge-tree/src/textSegment.ts +++ b/packages/dds/merge-tree/src/textSegment.ts @@ -25,6 +25,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; @@ -48,8 +49,6 @@ export class TextSegment extends BaseSegment { return undefined; } - public readonly type = TextSegment.type; - constructor(public text: string) { super(); this.cachedLength = text.length; diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 340a3c1c169a..2c04ced66c17 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -12,6 +12,7 @@ import { IMergeBlock, IMergeNode, ISegment, + Marker, MaxNodesInBlock, seqLTE, toRemovalInfo, @@ -32,11 +33,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; @@ -155,6 +156,10 @@ function scourNode(node: IMergeBlock, holdNodes: IMergeNode[], mergeTree: MergeT ); segment.parent = undefined; + + if (Marker.is(segment)) { + mergeTree.unlinkMarker(segment); + } } else { holdNodes.push(segment); } From 2c4a9167e409218e4bca19d8d3ebb57332eb3202 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Wed, 22 Nov 2023 10:21:21 -0800 Subject: [PATCH 29/50] Remove containerInteractions.ts (#18432) ### 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. [AB#5499](https://dev.azure.com/fluidframework/internal/_workitems/edit/5499) --- .changeset/bumpy-papayas-invite.md | 9 ++ .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../aqueduct/api-report/aqueduct.api.md | 10 --- packages/framework/aqueduct/package.json | 15 +++- packages/framework/aqueduct/src/index.ts | 5 -- .../validateAqueductPrevious.generated.ts | 48 ++-------- .../src/utils/containerInteractions.ts | 89 ------------------- .../framework/aqueduct/src/utils/index.ts | 10 --- .../src/test/loadModes.spec.ts | 9 +- 9 files changed, 34 insertions(+), 163 deletions(-) create mode 100644 .changeset/bumpy-papayas-invite.md delete mode 100644 packages/framework/aqueduct/src/utils/containerInteractions.ts delete mode 100644 packages/framework/aqueduct/src/utils/index.ts 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 728305eb318d..9d147a31ca7f 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -115,5 +115,5 @@ const entryPoint = await container.getEntryPoint(); | `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 | 2.0.0-internal.8.0.0 | -| `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` | 2.0.0-internal.7.0.0 | | +| `getDefaultObjectFromContainer`, `getObjectWithIdFromContainer` and `getObjectFromContainer` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.0.0 | diff --git a/packages/framework/aqueduct/api-report/aqueduct.api.md b/packages/framework/aqueduct/api-report/aqueduct.api.md index bac5348656df..9e3b27952b97 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'; @@ -101,15 +100,6 @@ export function defaultFluidObjectRequestHandler(fluidObject: FluidObject, reque // @public @deprecated export const defaultRouteRequestHandler: (defaultRootId: string) => (request: IRequest, runtime: IContainerRuntime) => Promise; -// @public @deprecated (undocumented) -export function getDefaultObjectFromContainer(container: IContainer): Promise; - -// @public @deprecated (undocumented) -export function getObjectFromContainer(path: string, container: IContainer): Promise; - -// @public @deprecated (undocumented) -export function getObjectWithIdFromContainer(id: string, container: IContainer): Promise; - // @public (undocumented) export interface IDataObjectProps { // (undocumented) diff --git a/packages/framework/aqueduct/package.json b/packages/framework/aqueduct/package.json index df28782f2adb..093346684644 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -104,6 +104,19 @@ }, "module:es5": "es5/index.js", "typeValidation": { - "broken": {} + "broken": { + "RemovedFunctionDeclaration_getDefaultObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_getObjectFromContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedFunctionDeclaration_getObjectWithIdFromContainer": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/framework/aqueduct/src/index.ts b/packages/framework/aqueduct/src/index.ts index ad64290748d1..77611f9e24eb 100644 --- a/packages/framework/aqueduct/src/index.ts +++ b/packages/framework/aqueduct/src/index.ts @@ -33,8 +33,3 @@ export { defaultRouteRequestHandler, mountableViewRequestHandler, } from "./request-handlers"; -export { - getDefaultObjectFromContainer, - getObjectFromContainer, - getObjectWithIdFromContainer, -} from "./utils"; diff --git a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts index 15158edbcfaa..edb3e149ebbb 100644 --- a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts +++ b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts @@ -288,74 +288,38 @@ use_old_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); -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); -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); -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); -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); -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); -use_old_FunctionDeclaration_getObjectWithIdFromContainer( - get_current_FunctionDeclaration_getObjectWithIdFromContainer()); /* * Validate forward compat by using old type in place of current type diff --git a/packages/framework/aqueduct/src/utils/containerInteractions.ts b/packages/framework/aqueduct/src/utils/containerInteractions.ts deleted file mode 100644 index f3a41cb12a3c..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 - * @public - */ -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 - * @public - */ -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 - * @public - */ -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/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 59162e1c2276..594fc7805e61 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 @@ -9,7 +9,6 @@ import { DataObject, DataObjectFactory, IDataObjectProps, - getDefaultObjectFromContainer, } from "@fluidframework/aqueduct"; import { IContainer, LoaderHeader } from "@fluidframework/container-definitions"; import { IFluidHandle, IRequestHeader } from "@fluidframework/core-interfaces"; @@ -117,7 +116,7 @@ describeNoCompat("LoadModes", (getTestObjectProvider) => { beforeEach(async () => { documentId = createDocumentId(); container1 = await createContainer(); - dataObject1 = await getDefaultObjectFromContainer(container1); + dataObject1 = (await container1.getEntryPoint()) as TestDataObject; }); afterEach(() => { @@ -206,7 +205,7 @@ describeNoCompat("LoadModes", (getTestObjectProvider) => { headers, ); const initialSequenceNumber = container2.deltaManager.lastSequenceNumber; - const dataObject2 = await getDefaultObjectFromContainer(container2); + const dataObject2 = (await container2.getEntryPoint()) as TestDataObject; const initialValue = dataObject2.value; assert.strictEqual(dataObject1.value, dataObject2.value, "counter values should be equal"); @@ -260,7 +259,7 @@ describeNoCompat("LoadModes", (getTestObjectProvider) => { testDataObjectFactory, headers, ); - const dataObject2 = await getDefaultObjectFromContainer(container2); + const dataObject2 = (await container2.getEntryPoint()) as TestDataObject; assert.strictEqual( sequenceNumber, @@ -325,7 +324,7 @@ describeNoCompat("LoadModes", (getTestObjectProvider) => { testDataObjectFactory, headers, ); - const dataObject2 = await getDefaultObjectFromContainer(container2); + const dataObject2 = (await container2.getEntryPoint()) as TestDataObject; assert.strictEqual( sequenceNumber, From 8675798423caa1fbeec279b3cc306a6f22221020 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:36:59 -0800 Subject: [PATCH 30/50] refactor(merge-tree sequence): remove combining op and related codepaths (#18096) Removes ICombiningOp and code gated on different values of it. This code has no code coverage and is generally unused by partners. Changeset+deprecation PR coming after CI passes. I'm considering modifying some of the APIs to accept `undefined` in the place of the combining op to reduce code churn, but based on a quick glance of partner code, this likely won't be necessary or useful. --- .changeset/silent-coins-report.md | 12 +++ .../table-document/src/document.ts | 20 ++--- .../data-objects/table-document/src/slice.ts | 20 ++--- .../data-objects/table-document/src/table.ts | 6 +- .../merge-tree/api-report/merge-tree.api.md | 35 ++------ packages/dds/merge-tree/package.json | 4 + packages/dds/merge-tree/src/client.ts | 80 ++--------------- packages/dds/merge-tree/src/index.ts | 1 - packages/dds/merge-tree/src/localReference.ts | 8 +- packages/dds/merge-tree/src/mergeTree.ts | 53 +---------- packages/dds/merge-tree/src/mergeTreeNodes.ts | 21 +---- packages/dds/merge-tree/src/opBuilder.ts | 11 --- packages/dds/merge-tree/src/ops.ts | 18 ---- packages/dds/merge-tree/src/properties.ts | 82 ++--------------- .../dds/merge-tree/src/referencePositions.ts | 7 +- .../src/segmentPropertiesManager.ts | 87 +++---------------- .../src/test/attributionPolicy.spec.ts | 16 ++-- .../src/test/client.applyMsg.spec.ts | 24 ++--- .../src/test/client.attributionFarm.spec.ts | 2 +- .../src/test/client.rollback.spec.ts | 60 +++++-------- .../dds/merge-tree/src/test/client.spec.ts | 2 +- packages/dds/merge-tree/src/test/index.ts | 1 - .../mergeTree.annotate.deltaCallback.spec.ts | 6 -- .../src/test/mergeTree.annotate.spec.ts | 34 +------- .../src/test/mergeTreeOperationRunner.ts | 2 +- .../src/test/resetPendingSegmentsToOp.spec.ts | 8 +- .../merge-tree/src/test/revertibles.spec.ts | 28 +----- .../dds/merge-tree/src/test/snapshot.utils.ts | 2 +- .../src/test/snapshotlegacy.spec.ts | 2 +- .../dds/merge-tree/src/test/testClient.ts | 9 +- .../validateMergeTreePrevious.generated.ts | 16 +--- .../dds/sequence/api-report/sequence.api.md | 11 +-- .../dds/sequence/src/intervals/interval.ts | 3 - .../sequence/src/intervals/intervalUtils.ts | 1 - .../src/intervals/sequenceInterval.ts | 4 +- packages/dds/sequence/src/sequence.ts | 12 +-- packages/dds/sequence/src/sharedString.ts | 26 +----- .../src/test/sequenceDeltaEvent.spec.ts | 61 +++++-------- .../drivers/debugger/src/messageSchema.ts | 11 --- .../sequence/sharedStringWithInterception.ts | 50 +---------- 40 files changed, 157 insertions(+), 699 deletions(-) create mode 100644 .changeset/silent-coins-report.md 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/examples/data-objects/table-document/src/document.ts b/examples/data-objects/table-document/src/document.ts index 4ac3c61639b7..edefd8123715 100644 --- a/examples/data-objects/table-document/src/document.ts +++ b/examples/data-objects/table-document/src/document.ts @@ -5,7 +5,7 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IEvent, IFluidHandle } from "@fluidframework/core-interfaces"; -import { ICombiningOp, ReferencePosition, PropertySet } from "@fluidframework/merge-tree"; +import { ReferencePosition, PropertySet } from "@fluidframework/merge-tree"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { IntervalType, SequenceDeltaEvent } from "@fluidframework/sequence"; import { @@ -103,26 +103,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/slice.ts b/examples/data-objects/table-document/src/slice.ts index b49cb4da3c6f..493dc4d05d02 100644 --- a/examples/data-objects/table-document/src/slice.ts +++ b/examples/data-objects/table-document/src/slice.ts @@ -5,7 +5,7 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { ICombiningOp, PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/merge-tree"; import { handleFromLegacyUri } from "@fluidframework/request-handler"; import { CellRange } from "./cellrange"; import { TableSliceType } from "./componentTypes"; @@ -67,15 +67,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 { @@ -83,15 +78,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 { diff --git a/examples/data-objects/table-document/src/table.ts b/examples/data-objects/table-document/src/table.ts index 87e26d4bf2ca..660a5dfb9592 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/merge-tree"; export type TableDocumentItem = any; @@ -17,9 +17,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/packages/dds/merge-tree/api-report/merge-tree.api.md b/packages/dds/merge-tree/api-report/merge-tree.api.md index a8f901c1c34e..9d90fdce3f4e 100644 --- a/packages/dds/merge-tree/api-report/merge-tree.api.md +++ b/packages/dds/merge-tree/api-report/merge-tree.api.md @@ -17,7 +17,7 @@ import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; import { TypedEventEmitter } from '@fluid-internal/client-utils'; // @internal (undocumented) -export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet, op?: ICombiningOp, seq?: number): PropertySet; +export function addProperties(oldProps: PropertySet | undefined, newProps: PropertySet): PropertySet; // @alpha (undocumented) export function appendToMergeTreeDeltaRevertibles(deltaArgs: IMergeTreeDeltaCallbackArgs, revertibles: MergeTreeDeltaRevertible[]): void; @@ -39,7 +39,7 @@ export abstract class BaseSegment extends MergeNode implements ISegment { // @internal (undocumented) ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) protected addSerializedProps(jseg: IJSONSegment): void; // (undocumented) @@ -103,10 +103,8 @@ export class Client extends TypedEventEmitter { 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) @@ -218,7 +216,7 @@ export function compareReferencePositions(a: ReferencePosition, b: ReferencePosi export type ConflictAction = (key: TKey, currentKey: TKey, data: TData, currentData: TData) => QProperty; // @internal -export function createAnnotateRangeOp(start: number, end: number, props: PropertySet, combiningOp: ICombiningOp | undefined): IMergeTreeAnnotateMsg; +export function createAnnotateRangeOp(start: number, end: number, props: PropertySet): IMergeTreeAnnotateMsg; // @public (undocumented) export function createDetachedLocalReferencePosition(refType?: ReferenceType): LocalReferencePosition; @@ -320,18 +318,6 @@ export interface IAttributionCollectionSpec { }>; } -// @public @deprecated (undocumented) -export interface ICombiningOp { - // (undocumented) - defaultValue?: any; - // (undocumented) - maxValue?: any; - // (undocumented) - minValue?: any; - // (undocumented) - name: string; -} - // @public (undocumented) export interface IJSONMarkerSegment extends IJSONSegment { // (undocumented) @@ -366,8 +352,6 @@ export interface IMergeNodeCommon { // @public (undocumented) export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { - // @deprecated (undocumented) - combiningOp?: ICombiningOp; // (undocumented) pos1?: number; // (undocumented) @@ -550,7 +534,7 @@ export interface IRemovalInfo { export interface ISegment extends IMergeNodeCommon, Partial, Partial { // @internal ack(segmentGroup: SegmentGroup, opArgs: IMergeTreeDeltaOpArgs): boolean; - addProperties(newProps: PropertySet, op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet | undefined; + addProperties(newProps: PropertySet, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback): PropertySet; // (undocumented) append(segment: ISegment): void; // @alpha @@ -772,11 +756,10 @@ export function minReferencePosition(a: T, b: T): T // @public (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) @@ -788,8 +771,6 @@ export class PropertiesManager { // @public (undocumented) export enum PropertiesRollback { None = 0, - // @deprecated - Rewrite = 2, Rollback = 1 } @@ -898,7 +879,7 @@ export class RedBlackTree implements SortedDictionary // @public export interface ReferencePosition { // (undocumented) - addProperties(newProps: PropertySet, op?: ICombiningOp): void; + addProperties(newProps: PropertySet): void; getOffset(): number; getSegment(): ISegment | undefined; // (undocumented) diff --git a/packages/dds/merge-tree/package.json b/packages/dds/merge-tree/package.json index 27d67ffc6d4b..6a0820e9725c 100644 --- a/packages/dds/merge-tree/package.json +++ b/packages/dds/merge-tree/package.json @@ -246,6 +246,10 @@ "ClassDeclaration_TextSegment": { "backCompat": false }, + "RemovedInterfaceDeclaration_ICombiningOp": { + "forwardCompat": false, + "backCompat": false + }, "RemovedFunctionDeclaration_createAnnotateMarkerOp": { "forwardCompat": false, "backCompat": false diff --git a/packages/dds/merge-tree/src/client.ts b/packages/dds/merge-tree/src/client.ts index fac439d02467..4343012910b4 100644 --- a/packages/dds/merge-tree/src/client.ts +++ b/packages/dds/merge-tree/src/client.ts @@ -22,7 +22,6 @@ import { LocalReferencePosition, SlidingPreference } from "./localReference"; import { CollaborationWindow, compareStrings, - IConsensusInfo, IMoveInfo, IMergeLeaf, ISegment, @@ -40,8 +39,6 @@ import { createRemoveRangeOp, } from "./opBuilder"; import { - // eslint-disable-next-line import/no-deprecated - ICombiningOp, IJSONSegment, IMergeTreeAnnotateMsg, IMergeTreeDeltaOp, @@ -128,7 +125,6 @@ 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 @@ -177,55 +173,14 @@ 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 { - // eslint-disable-next-line import/no-deprecated - 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, - // eslint-disable-next-line import/no-deprecated - combiningOp?: ICombiningOp, - ): IMergeTreeAnnotateMsg | undefined { - const annotateOp = createAnnotateMarkerOp(marker, props, combiningOp)!; + public annotateMarker(marker: Marker, props: PropertySet): IMergeTreeAnnotateMsg | undefined { + const annotateOp = createAnnotateMarkerOp(marker, props)!; this.applyAnnotateRangeOp({ op: annotateOp }); return annotateOp; } @@ -235,17 +190,14 @@ export class Client extends TypedEventEmitter { * @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, - // eslint-disable-next-line import/no-deprecated - combiningOp: ICombiningOp | undefined, ): IMergeTreeAnnotateMsg | undefined { - const annotateOp = createAnnotateRangeOp(start, end, props, combiningOp); + const annotateOp = createAnnotateRangeOp(start, end, props); this.applyAnnotateRangeOp({ op: annotateOp }); return annotateOp; } @@ -538,7 +490,6 @@ export class Client extends TypedEventEmitter { range.start, range.end, op.props, - op.combiningOp, clientArgs.referenceSequenceNumber, clientArgs.clientId, clientArgs.sequenceNumber, @@ -700,25 +651,16 @@ 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); } } @@ -826,7 +768,6 @@ export class Client extends TypedEventEmitter { segmentPosition, segmentPosition + segment.cachedLength, resetOp.props, - resetOp.combiningOp, ); } break; @@ -1194,17 +1135,6 @@ export class Client extends TypedEventEmitter { } } - private 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); } diff --git a/packages/dds/merge-tree/src/index.ts b/packages/dds/merge-tree/src/index.ts index 29994acc4c6b..66757e893d5b 100644 --- a/packages/dds/merge-tree/src/index.ts +++ b/packages/dds/merge-tree/src/index.ts @@ -82,7 +82,6 @@ export { createObliterateRangeOp, } from "./opBuilder"; export { - ICombiningOp, IJSONSegment, IMarkerDef, IMergeTreeAnnotateMsg, diff --git a/packages/dds/merge-tree/src/localReference.ts b/packages/dds/merge-tree/src/localReference.ts index dcbe1cae2a27..938a1f57a838 100644 --- a/packages/dds/merge-tree/src/localReference.ts +++ b/packages/dds/merge-tree/src/localReference.ts @@ -3,14 +3,12 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - import { assert } from "@fluidframework/core-utils"; import { UsageError } from "@fluidframework/telemetry-utils"; import { DoublyLinkedList, ListNode, walkList } from "./collections"; import { ISegment } from "./mergeTreeNodes"; import { TrackingGroup, TrackingGroupCollection } from "./mergeTreeTracking"; -import { ICombiningOp, ReferenceType } from "./ops"; +import { ReferenceType } from "./ops"; import { addProperties, PropertySet } from "./properties"; import { ReferencePosition, refTypeIncludesFlag } from "./referencePositions"; @@ -124,8 +122,8 @@ class LocalReference implements LocalReferencePosition { return false; } - public addProperties(newProps: PropertySet, op?: ICombiningOp) { - this.properties = addProperties(this.properties, newProps, op); + public addProperties(newProps: PropertySet) { + this.properties = addProperties(this.properties, newProps); } public getSegment() { diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index e60fc1141be3..64ab58478baf 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -39,7 +39,6 @@ import { Marker, MaxNodesInBlock, MergeBlock, - MinListener, reservedMarkerIdKey, SegmentActions, SegmentGroup, @@ -55,14 +54,7 @@ import { MergeTreeMaintenanceType, } from "./mergeTreeDeltaCallback"; import { createAnnotateRangeOp, createInsertSegmentOp, createRemoveRangeOp } from "./opBuilder"; -import { - // eslint-disable-next-line import/no-deprecated - ICombiningOp, - IMergeTreeDeltaOp, - IRelativePosition, - MergeTreeDeltaType, - ReferenceType, -} from "./ops"; +import { IMergeTreeDeltaOp, IRelativePosition, MergeTreeDeltaType, ReferenceType } from "./ops"; import { PartialSequenceLengths } from "./partialLengths"; import { createMap, extend, extendIfUndefined, MapLike, PropertySet } from "./properties"; import { @@ -91,16 +83,6 @@ import { EndOfTreeSegment, StartOfTreeSegment } from "./endOfTreeSegment"; */ export type ISegmentLeaf = ISegment & IMergeLeaf; -const minListenerComparer: IComparer = { - 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 && @@ -509,7 +491,6 @@ export class MergeTree { // 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 idToMarker = new Map(); - private minSeqListeners: Heap | undefined; public mergeTreeDeltaCallback?: MergeTreeDeltaCallback; public mergeTreeMaintenanceCallback?: MergeTreeMaintenanceCallback; @@ -1100,23 +1081,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()!.value.minRequired <= this.collabWindow.minSeq - ) { - const minListener = this.minSeqListeners.get()!; - minListener.onMinGE(this.collabWindow.minSeq); - } - } - } - public setMinSeq(minSeq: number) { assert( minSeq <= this.collabWindow.currentSeq, @@ -1136,7 +1100,6 @@ export class MergeTree { if (MergeTree.options.zamboniSegments) { zamboniSegments(this); } - this.notifyMinSeqListeners(); } } @@ -2056,7 +2019,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 @@ -2067,8 +2029,6 @@ export class MergeTree { start: number, end: number, props: PropertySet, - // eslint-disable-next-line import/no-deprecated - combiningOp: ICombiningOp | undefined, refSeq: number, clientId: number, seq: number, @@ -2090,7 +2050,6 @@ export class MergeTree { ); const propertyDeltas = segment.addProperties( props, - combiningOp, seq, this.collabWindow.collaborating, rollback, @@ -2102,7 +2061,7 @@ export class MergeTree { segment, segmentGroup, localSeq, - propertyDeltas ?? {}, + propertyDeltas, ); } else { if (MergeTree.options.zamboniSegments) { @@ -2439,26 +2398,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++; } diff --git a/packages/dds/merge-tree/src/mergeTreeNodes.ts b/packages/dds/merge-tree/src/mergeTreeNodes.ts index 7f21481f45c8..67384bdb42e0 100644 --- a/packages/dds/merge-tree/src/mergeTreeNodes.ts +++ b/packages/dds/merge-tree/src/mergeTreeNodes.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { assert } from "@fluidframework/core-utils"; @@ -14,7 +13,7 @@ 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"; import { clone, createMap, MapLike, PropertySet } from "./properties"; @@ -283,11 +282,10 @@ export interface ISegment extends IMergeNodeCommon, Partial, Parti */ addProperties( newProps: PropertySet, - op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback?: PropertiesRollback, - ): PropertySet | undefined; + ): PropertySet; clone(): ISegment; canAppend(segment: ISegment): boolean; append(segment: ISegment): void; @@ -477,7 +475,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment { public addProperties( newProps: PropertySet, - op?: ICombiningOp, seq?: number, collaborating?: boolean, rollback: PropertiesRollback = PropertiesRollback.None, @@ -487,7 +484,6 @@ export abstract class BaseSegment extends MergeNode implements ISegment { return this.propertyManager.addProperties( this.properties, newProps, - op, seq, collaborating, rollback, @@ -792,19 +788,6 @@ export const compareNumbers = (a: number, b: number) => a - b; export const compareStrings = (a: string, b: string) => a.localeCompare(b); -export interface IConsensusInfo { - marker: Marker; - callback: (m: Marker) => void; -} - -/** - * @internal - */ -export interface MinListener { - minRequired: number; - onMinGE(minSeq: number): void; -} - /** * Get a human-readable string for a given {@link Marker}. * diff --git a/packages/dds/merge-tree/src/opBuilder.ts b/packages/dds/merge-tree/src/opBuilder.ts index e806951cb7e9..a3b105401953 100644 --- a/packages/dds/merge-tree/src/opBuilder.ts +++ b/packages/dds/merge-tree/src/opBuilder.ts @@ -5,8 +5,6 @@ import { ISegment, Marker } from "./mergeTreeNodes"; import { - // eslint-disable-next-line import/no-deprecated - ICombiningOp, IMergeTreeAnnotateMsg, // eslint-disable-next-line import/no-deprecated IMergeTreeGroupMsg, @@ -22,7 +20,6 @@ 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 * * @internal @@ -30,8 +27,6 @@ import { PropertySet } from "./properties"; export function createAnnotateMarkerOp( marker: Marker, props: PropertySet, - // eslint-disable-next-line import/no-deprecated - combiningOp?: ICombiningOp, ): IMergeTreeAnnotateMsg | undefined { const id = marker.getId(); if (!id) { @@ -39,7 +34,6 @@ export function createAnnotateMarkerOp( } return { - combiningOp, props, relativePos1: { id, before: true }, relativePos2: { id }, @@ -52,7 +46,6 @@ 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 * * @internal @@ -61,11 +54,8 @@ export function createAnnotateRangeOp( start: number, end: number, props: PropertySet, - // eslint-disable-next-line import/no-deprecated - combiningOp: ICombiningOp | undefined, ): IMergeTreeAnnotateMsg { return { - combiningOp, pos1: start, pos2: end, props, @@ -141,7 +131,6 @@ export function createInsertOp(pos: number, segSpec: any): IMergeTreeInsertMsg { export function createGroupOp(...ops: IMergeTreeDeltaOp[]): IMergeTreeGroupMsg { return { ops, - // eslint-disable-next-line import/no-deprecated type: MergeTreeDeltaType.GROUP, }; } diff --git a/packages/dds/merge-tree/src/ops.ts b/packages/dds/merge-tree/src/ops.ts index c0571733bd50..64590f631e51 100644 --- a/packages/dds/merge-tree/src/ops.ts +++ b/packages/dds/merge-tree/src/ops.ts @@ -124,18 +124,6 @@ export interface IMergeTreeObliterateMsg extends IMergeTreeDelta { relativePos2?: never; } -/** - * @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. - */ -export interface ICombiningOp { - name: string; - defaultValue?: any; - minValue?: any; - maxValue?: any; -} - export interface IMergeTreeAnnotateMsg extends IMergeTreeDelta { type: typeof MergeTreeDeltaType.ANNOTATE; pos1?: number; @@ -143,12 +131,6 @@ 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; } /** diff --git a/packages/dds/merge-tree/src/properties.ts b/packages/dds/merge-tree/src/properties.ts index 7d84efd1e420..a26dd47a0d48 100644 --- a/packages/dds/merge-tree/src/properties.ts +++ b/packages/dds/merge-tree/src/properties.ts @@ -3,10 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ - -import { ICombiningOp } from "./ops"; - /** * Any mapping from a string to values of type `T` */ @@ -25,58 +21,6 @@ export interface MapLike { */ export type PropertySet = MapLike; -export interface IConsensusValue { - seq: number; - value: any; -} - -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; -} - /** * @internal */ @@ -107,12 +51,7 @@ export function matchProperties(a: PropertySet | undefined, b: PropertySet | und return true; } -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) { @@ -121,10 +60,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; } } } @@ -149,17 +85,9 @@ export function clone(extension: MapLike | undefined) { /** * @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 7c6480dfc68a..5415fc63cb38 100644 --- a/packages/dds/merge-tree/src/referencePositions.ts +++ b/packages/dds/merge-tree/src/referencePositions.ts @@ -5,8 +5,7 @@ import { SlidingPreference } from "./localReference"; import { ISegment } from "./mergeTreeNodes"; -// eslint-disable-next-line import/no-deprecated -import { ReferenceType, ICombiningOp } from "./ops"; +import { ReferenceType } from "./ops"; import { PropertySet } from "./properties"; export const reservedTileLabelsKey = "referenceTileLabels"; @@ -74,12 +73,10 @@ 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. */ - // eslint-disable-next-line import/no-deprecated - addProperties(newProps: PropertySet, op?: ICombiningOp): void; + addProperties(newProps: PropertySet): void; isLeaf(): this is ISegment; } diff --git a/packages/dds/merge-tree/src/segmentPropertiesManager.ts b/packages/dds/merge-tree/src/segmentPropertiesManager.ts index b16a041575f0..d553b12c4962 100644 --- a/packages/dds/merge-tree/src/segmentPropertiesManager.ts +++ b/packages/dds/merge-tree/src/segmentPropertiesManager.ts @@ -3,13 +3,12 @@ * 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"; +import { createMap, MapLike, PropertySet } from "./properties"; export enum PropertiesRollback { /** Not in a rollback */ @@ -17,40 +16,18 @@ 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, } 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!" */, @@ -67,43 +44,22 @@ export class PropertiesManager { public addProperties( oldProps: PropertySet, newProps: PropertySet, - op?: ICombiningOp, seq?: number, collaborating: boolean = false, rollback: PropertiesRollback = PropertiesRollback.None, - ): PropertySet | undefined { + ): PropertySet { 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; } @@ -111,30 +67,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; } @@ -147,9 +83,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]; @@ -175,7 +109,6 @@ export class PropertiesManager { for (const key of Object.keys(oldProps)) { newProps[key] = oldProps[key]; } - newManager.pendingRewriteCount = this.pendingRewriteCount; newManager.pendingKeyUpdateCount = createMap(); for (const key of Object.keys(this.pendingKeyUpdateCount!)) { newManager.pendingKeyUpdateCount[key] = this.pendingKeyUpdateCount![key]; @@ -185,7 +118,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/test/attributionPolicy.spec.ts b/packages/dds/merge-tree/src/test/attributionPolicy.spec.ts index da88b40a8d6b..ec28cde38bf4 100644 --- a/packages/dds/merge-tree/src/test/attributionPolicy.spec.ts +++ b/packages/dds/merge-tree/src/test/attributionPolicy.spec.ts @@ -46,11 +46,11 @@ describe("Attribution Policy", () => { 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/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.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.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.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/index.ts b/packages/dds/merge-tree/src/test/index.ts index 57ca337244a2..1c0e2b179c42 100644 --- a/packages/dds/merge-tree/src/test/index.ts +++ b/packages/dds/merge-tree/src/test/index.ts @@ -59,7 +59,6 @@ export { debugMarkerToString, DetachedReferencePosition, discardMergeTreeDeltaRevertible, - ICombiningOp, IJSONMarkerSegment, IJSONSegment, IMarkerDef, 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 141d24c3fc7f..bed4c4c15d19 100644 --- a/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts +++ b/packages/dds/merge-tree/src/test/mergeTree.annotate.spec.ts @@ -7,7 +7,7 @@ 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"; @@ -56,7 +56,6 @@ describe("MergeTree", () => { { propertySource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, currentSequenceNumber + 1, @@ -79,7 +78,6 @@ describe("MergeTree", () => { { propertySource: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -112,7 +110,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -137,7 +134,6 @@ describe("MergeTree", () => { { secondProperty: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -174,7 +170,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, secondChangeProps, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -189,7 +184,6 @@ describe("MergeTree", () => { splitPos, annotateEnd, splitOnlyProps, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -295,7 +289,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -357,7 +350,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -394,7 +386,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props2, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -411,7 +402,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props3, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -478,7 +468,6 @@ describe("MergeTree", () => { { secondSource: "local2", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -505,7 +494,6 @@ describe("MergeTree", () => { remoteOnly: 1, secondSource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -533,7 +521,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -578,7 +565,6 @@ describe("MergeTree", () => { { propertySource: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -611,7 +597,6 @@ describe("MergeTree", () => { annotateStart, annotateEnd, props, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -641,15 +626,11 @@ describe("MergeTree", () => { const props = { propertySource: "local", }; - const combiningOp: ICombiningOp = { - name: "rewrite", - }; beforeEach(() => { mergeTree.annotateRange( annotateStart, annotateEnd, props, - combiningOp, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -665,7 +646,6 @@ describe("MergeTree", () => { propertySource: "local2", secondProperty: "local", }, - undefined, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -690,7 +670,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -706,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, @@ -730,7 +708,6 @@ describe("MergeTree", () => { propertySource: "remote", remoteProperty: 1, }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -756,7 +733,6 @@ describe("MergeTree", () => { { secondSource: "local2", }, - combiningOp, currentSequenceNumber, localClientId, UnassignedSequenceNumber, @@ -765,7 +741,6 @@ describe("MergeTree", () => { mergeTree.ackPendingSegment({ op: { - combiningOp, pos1: annotateStart, pos2: annotateEnd, props, @@ -784,7 +759,6 @@ describe("MergeTree", () => { remoteOnly: 1, secondSource: "remote", }, - undefined, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, @@ -798,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/mergeTreeOperationRunner.ts b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts index 5d72bc6335f5..f8cd270e3268 100644 --- a/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts +++ b/packages/dds/merge-tree/src/test/mergeTreeOperationRunner.ts @@ -33,7 +33,7 @@ export const obliterateRange: TestOperation = ( ) => 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/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 9fbea8a02601..1b063e55708b 100644 --- a/packages/dds/merge-tree/src/test/revertibles.spec.ts +++ b/packages/dds/merge-tree/src/test/revertibles.spec.ts @@ -280,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" }); @@ -390,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 @@ -432,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.utils.ts b/packages/dds/merge-tree/src/test/snapshot.utils.ts index 92123caa68a6..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) { 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 7f81e2e52cbe..f7d98c6c1937 100644 --- a/packages/dds/merge-tree/src/test/testClient.ts +++ b/packages/dds/merge-tree/src/test/testClient.ts @@ -256,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), ); } @@ -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/types/validateMergeTreePrevious.generated.ts b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts index ad9a3308024d..c383592d85f6 100644 --- a/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts +++ b/packages/dds/merge-tree/src/test/types/validateMergeTreePrevious.generated.ts @@ -267,26 +267,14 @@ 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 diff --git a/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 76c43f53aa25..97f80d06d2ac 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'; @@ -205,7 +204,7 @@ export interface InteriorSequencePlace { export class Interval implements ISerializableInterval { constructor(start: number, end: number, props?: PropertySet); // @internal (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; // @internal (undocumented) auxProps: PropertySet[] | undefined; @@ -425,7 +424,7 @@ export class SequenceInterval implements ISerializableInterval { // @internal addPositionChangeListeners(beforePositionChange: () => void, afterPositionChange: () => void): void; // @internal (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; @@ -539,7 +538,7 @@ export class SharedIntervalCollectionFactory implements IChannelFactory { // @public (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) @@ -621,9 +620,7 @@ export class SharedSequence extends SharedSegmentSequence> { // @public 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): { diff --git a/packages/dds/sequence/src/intervals/interval.ts b/packages/dds/sequence/src/intervals/interval.ts index 2ba00a5562e8..513721724e9f 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, @@ -174,13 +173,11 @@ export class Interval implements ISerializableInterval { newProps: PropertySet, collaborating: boolean = false, seq?: number, - op?: ICombiningOp, ): PropertySet | undefined { if (newProps) { return this.propertyManager.addProperties( this.properties, newProps, - op, seq, collaborating, ); diff --git a/packages/dds/sequence/src/intervals/intervalUtils.ts b/packages/dds/sequence/src/intervals/intervalUtils.ts index 5f4c59fe7233..23a08757e4f2 100644 --- a/packages/dds/sequence/src/intervals/intervalUtils.ts +++ b/packages/dds/sequence/src/intervals/intervalUtils.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable no-bitwise */ import { diff --git a/packages/dds/sequence/src/intervals/sequenceInterval.ts b/packages/dds/sequence/src/intervals/sequenceInterval.ts index b5e798b9be19..6811b694c0a6 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, @@ -338,9 +337,8 @@ export class SequenceInterval implements ISerializableInterval { newProps: PropertySet, collab: boolean = false, seq?: number, - op?: ICombiningOp, ): PropertySet | undefined { - return this.propertyManager.addProperties(this.properties, newProps, op, seq, collab); + return this.propertyManager.addProperties(this.properties, newProps, seq, collab); } /** diff --git a/packages/dds/sequence/src/sequence.ts b/packages/dds/sequence/src/sequence.ts index 5888787bd5c7..cf0f1143736a 100644 --- a/packages/dds/sequence/src/sequence.ts +++ b/packages/dds/sequence/src/sequence.ts @@ -19,7 +19,6 @@ import { createGroupOp, createInsertOp, createRemoveRangeOp, - ICombiningOp, IJSONSegment, IMergeTreeAnnotateMsg, IMergeTreeDeltaOp, @@ -163,7 +162,6 @@ export abstract class SharedSegmentSequence r.position, r.position + r.segment.cachedLength, props, - undefined, ), ); } @@ -343,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, - ): void { - 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) { diff --git a/packages/dds/sequence/src/sharedString.ts b/packages/dds/sequence/src/sharedString.ts index c16394a19fd2..3a6a67fb853b 100644 --- a/packages/dds/sequence/src/sharedString.ts +++ b/packages/dds/sequence/src/sharedString.ts @@ -4,7 +4,6 @@ */ import { - ICombiningOp, IMergeTreeTextHelper, IRelativePosition, ISegment, @@ -185,34 +184,13 @@ export class SharedString 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), - ); - } - /** * 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)); } /** 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/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/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts b/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts index e1c6b49b3a23..2663df499c09 100644 --- a/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts +++ b/packages/framework/dds-interceptions/src/sequence/sharedStringWithInterception.ts @@ -200,50 +200,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. @@ -255,11 +220,7 @@ export function createSharedStringWithInterception( context.containerRuntime.orderSequentially(() => { executingCallback = true; try { - sharedString.annotateMarker( - marker, - propertyInterceptionCallback(props), - combiningOp, - ); + sharedString.annotateMarker(marker, propertyInterceptionCallback(props)); } finally { executingCallback = false; } @@ -272,14 +233,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. @@ -291,12 +250,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; } From 990d3f8392786b315e26094ca18ed67dba25cd03 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:01:22 -0800 Subject: [PATCH 31/50] feat(sequence): re-export some merge-tree types from sequence (#18081) Conservative re-export of merge-tree types from sequence. This is the subset of types used by a partner codebase. It avoids re-exporting types or functions that contain explicit references to merge-tree, except in the case of `MergeTreeDeltaType`. --- examples/data-objects/codemirror/package.json | 1 - .../data-objects/codemirror/src/codeMirror.ts | 3 +- .../codemirror/src/codeMirrorView.tsx | 6 ++- examples/data-objects/monaco/package.json | 1 - examples/data-objects/monaco/src/view.tsx | 8 +++- .../prosemirror/src/fluidBridge.ts | 12 ++---- .../prosemirror/src/fluidCollabManager.ts | 15 +++----- .../prosemirror/src/prosemirror.tsx | 38 +++++-------------- examples/data-objects/smde/package.json | 1 - examples/data-objects/smde/src/smde.ts | 3 +- examples/data-objects/smde/src/smdeView.tsx | 4 +- .../data-objects/table-document/package.json | 1 - .../table-document/src/cellrange.ts | 3 +- .../table-document/src/document.ts | 8 +++- .../src/interception/tableWithInterception.ts | 2 +- .../data-objects/table-document/src/slice.ts | 2 +- .../data-objects/table-document/src/table.ts | 2 +- .../src/test/tableWithInterception.spec.mts | 2 +- .../webflow/src/document/index.ts | 20 +++++----- .../webflow/src/document/segmentspan.ts | 2 +- .../data-objects/webflow/src/editor/caret.ts | 2 +- examples/data-objects/webflow/src/html/css.ts | 2 +- .../webflow/src/html/formatters.ts | 2 +- .../webflow/src/test/segmentspan.spec.ts | 2 +- .../data-objects/webflow/src/util/attr.ts | 2 +- .../data-objects/webflow/src/util/localref.ts | 2 +- .../data-objects/webflow/src/util/segment.ts | 2 +- .../data-objects/webflow/src/view/layout.ts | 6 +-- .../dds/sequence/api-report/sequence.api.md | 34 +++++++++++++++++ packages/dds/sequence/src/index.ts | 17 +++++++++ pnpm-lock.yaml | 8 ---- 31 files changed, 114 insertions(+), 99 deletions(-) diff --git a/examples/data-objects/codemirror/package.json b/examples/data-objects/codemirror/package.json index b7ac1a055752..14816cf9b43b 100644 --- a/examples/data-objects/codemirror/package.json +++ b/examples/data-objects/codemirror/package.json @@ -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 84844ac176d7..9c3f50760356 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 1a5f49dfe646..234176061611 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/monaco/package.json b/examples/data-objects/monaco/package.json index a6674efd0599..0f4bbbe1430b 100644 --- a/examples/data-objects/monaco/package.json +++ b/examples/data-objects/monaco/package.json @@ -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/prosemirror/src/fluidBridge.ts b/examples/data-objects/prosemirror/src/fluidBridge.ts index 299df457f527..40fa6ae19547 100644 --- a/examples/data-objects/prosemirror/src/fluidBridge.ts +++ b/examples/data-objects/prosemirror/src/fluidBridge.ts @@ -6,21 +6,17 @@ /* 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, diff --git a/examples/data-objects/prosemirror/src/fluidCollabManager.ts b/examples/data-objects/prosemirror/src/fluidCollabManager.ts index 31192b5e07f5..f2c7b5d658dd 100644 --- a/examples/data-objects/prosemirror/src/fluidCollabManager.ts +++ b/examples/data-objects/prosemirror/src/fluidCollabManager.ts @@ -3,19 +3,12 @@ * Licensed under the MIT License. */ -/* eslint-disable import/no-deprecated */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { EventEmitter } from "events"; -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"; @@ -263,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); @@ -327,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 ba95571285d9..27a931c59ba4 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.Simple }, props: beginMarkerProps }, - pos1: beginMarkerPos, - type: MergeTreeDeltaType.INSERT, - }, - { - seg: { marker: { refType: ReferenceType.Simple }, props: endMarkerProps }, - pos1: endMarkerPos, - type: MergeTreeDeltaType.INSERT, - }, - ]; + text.insertMarker(endMarkerPos, ReferenceType.Simple, endMarkerProps); + text.insertMarker(beginMarkerPos, ReferenceType.Simple, beginMarkerProps); } /** @@ -112,8 +93,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/smde/package.json b/examples/data-objects/smde/package.json index 560eb711f4db..62827e199ce4 100644 --- a/examples/data-objects/smde/package.json +++ b/examples/data-objects/smde/package.json @@ -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 b97514c070d5..00fd2d90339a 100644 --- a/examples/data-objects/table-document/package.json +++ b/examples/data-objects/table-document/package.json @@ -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 93c3f5520625..628ba7a56bea 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 edefd8123715..955af02599b4 100644 --- a/examples/data-objects/table-document/src/document.ts +++ b/examples/data-objects/table-document/src/document.ts @@ -5,9 +5,13 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IEvent, IFluidHandle } from "@fluidframework/core-interfaces"; -import { ReferencePosition, PropertySet } from "@fluidframework/merge-tree"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; -import { IntervalType, SequenceDeltaEvent } from "@fluidframework/sequence"; +import { + IntervalType, + SequenceDeltaEvent, + ReferencePosition, + PropertySet, +} from "@fluidframework/sequence"; import { positionToRowCol, rowColToPosition, diff --git a/examples/data-objects/table-document/src/interception/tableWithInterception.ts b/examples/data-objects/table-document/src/interception/tableWithInterception.ts index 167469ab71ca..0dea72a5a61d 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 493dc4d05d02..07d59f96681b 100644 --- a/examples/data-objects/table-document/src/slice.ts +++ b/examples/data-objects/table-document/src/slice.ts @@ -5,7 +5,7 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IFluidHandle } from "@fluidframework/core-interfaces"; -import { PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/sequence"; import { handleFromLegacyUri } from "@fluidframework/request-handler"; import { CellRange } from "./cellrange"; import { TableSliceType } from "./componentTypes"; diff --git a/examples/data-objects/table-document/src/table.ts b/examples/data-objects/table-document/src/table.ts index 660a5dfb9592..60d6a69d4fb0 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 { PropertySet } from "@fluidframework/merge-tree"; +import { PropertySet } from "@fluidframework/sequence"; export type TableDocumentItem = any; 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 0aab844aa429..3a8422794c10 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 { describeLoaderCompat } from "@fluid-private/test-version-utils"; diff --git a/examples/data-objects/webflow/src/document/index.ts b/examples/data-objects/webflow/src/document/index.ts index 700fa3581093..bf1b9c848dd8 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 { @@ -15,16 +13,7 @@ import { createDetachedLocalReferencePosition, createRemoveRangeOp, IMergeTreeRemoveMsg, - ISegment, - LocalReferencePosition, - Marker, - MergeTreeDeltaType, - PropertySet, - ReferencePosition, - ReferenceType, refGetTileLabels, - reservedTileLabelsKey, - TextSegment, } from "@fluidframework/merge-tree"; import { IFluidDataStoreContext, @@ -35,6 +24,15 @@ import { SharedStringSegment, SequenceMaintenanceEvent, SequenceDeltaEvent, + ISegment, + LocalReferencePosition, + Marker, + MergeTreeDeltaType, + PropertySet, + ReferencePosition, + ReferenceType, + reservedTileLabelsKey, + TextSegment, } from "@fluidframework/sequence"; import { ISharedDirectory, SharedDirectory } from "@fluidframework/map"; import { clamp, TagName, TokenList } from "../util/index.js"; 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/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 312e57664abc..e44da9dc33c2 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/segmentspan.spec.ts b/examples/data-objects/webflow/src/test/segmentspan.spec.ts index 01fc1125cba1..7e0f1204ba09 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 { describeLoaderCompat } 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/packages/dds/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 97f80d06d2ac..ef3054a306be 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -28,21 +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 { 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'; // @alpha @@ -60,6 +66,8 @@ export function appendIntervalPropertyChangedToRevertibles(interval: SequenceInt // @alpha export function appendSharedStringDeltaToRevertibles(string: SharedString, delta: SequenceDeltaEvent, revertibles: SharedStringRevertible[]): void; +export { BaseSegment } + // @public (undocumented) export function createEndpointIndex(sharedString: SharedString): IEndpointIndex; @@ -323,6 +331,8 @@ export interface IOverlappingIntervalsIndex { operation: TOperation; @@ -392,6 +402,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 } + // @alpha export function revertSharedStringRevertibles(sharedString: SharedString, revertibles: SharedStringRevertible[]): void; @@ -708,4 +738,8 @@ export class SubSequence extends BaseSegment { static readonly typeString: string; } +export { TextSegment } + +export { TrackingGroup } + ``` 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/pnpm-lock.yaml b/pnpm-lock.yaml index 4dffbac5a79f..f12cf16a640a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1859,7 +1859,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:~ @@ -1894,7 +1893,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 @@ -2075,7 +2073,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 @@ -2104,7 +2101,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 @@ -2800,7 +2796,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:~ @@ -2835,7 +2830,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 @@ -2877,7 +2871,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:~ @@ -2906,7 +2899,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 From 43985574f57fe3873b80f02823eaf10312d3b322 Mon Sep 17 00:00:00 2001 From: Tony Murphy Date: Mon, 27 Nov 2023 14:08:44 -0800 Subject: [PATCH 32/50] Remove Deprecated FluidStatic Classes (#18489) 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 this is a follow up to #18402 Co-authored-by: Tony Murphy --- .changeset/wet-hornets-trade.md | 17 +++++++ .../api-report/azure-client.api.md | 8 ---- azure/packages/azure-client/package.json | 7 ++- .../azure-client/src/AzureAudience.ts | 26 +--------- azure/packages/azure-client/src/index.ts | 1 - .../validateAzureClientPrevious.generated.ts | 16 +------ .../external-controller/tests/index.ts | 11 +++-- .../api-report/fluid-framework.api.md | 9 ---- .../framework/fluid-framework/src/index.ts | 5 -- .../api-report/fluid-static.api.md | 41 ---------------- packages/framework/fluid-static/package.json | 15 +++++- .../fluid-static/src/fluidContainer.ts | 3 +- packages/framework/fluid-static/src/index.ts | 8 +--- .../fluid-static/src/rootDataObject.ts | 3 +- .../fluid-static/src/serviceAudience.ts | 27 +++-------- .../validateFluidStaticPrevious.generated.ts | 48 +++---------------- .../api-report/tinylicious-client.api.md | 8 ---- .../framework/tinylicious-client/package.json | 7 ++- .../src/TinyliciousAudience.ts | 21 +------- .../framework/tinylicious-client/src/index.ts | 1 - ...dateTinyliciousClientPrevious.generated.ts | 16 +------ 21 files changed, 73 insertions(+), 225 deletions(-) create mode 100644 .changeset/wet-hornets-trade.md 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/azure/packages/azure-client/api-report/azure-client.api.md b/azure/packages/azure-client/api-report/azure-client.api.md index c49c135012d9..cc01ac17b45f 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/telemetry-utils'; import { IFluidContainer } from '@fluidframework/fluid-static'; @@ -18,13 +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'; - -// @public @deprecated -export class AzureAudience extends ServiceAudience implements IAzureAudience { - // @internal - protected createServiceMember(audienceMember: IClient): AzureMember; -} // @public export class AzureClient { diff --git a/azure/packages/azure-client/package.json b/azure/packages/azure-client/package.json index a552d7a01ad6..c90c63e814ab 100644 --- a/azure/packages/azure-client/package.json +++ b/azure/packages/azure-client/package.json @@ -111,6 +111,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 2aabd39dd44a..65e47c1c9713 100644 --- a/azure/packages/azure-client/src/AzureAudience.ts +++ b/azure/packages/azure-client/src/AzureAudience.ts @@ -3,33 +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. - * - * @public - * @deprecated This class will be removed. use {@link IAzureAudience} instead - */ -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}. - * - * @internal - */ - 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/index.ts b/azure/packages/azure-client/src/index.ts index f5acdf9f79f6..b3bd057f9244 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/external-controller/tests/index.ts b/azure/packages/external-controller/tests/index.ts index af09a145538b..b2daa012c65b 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 @@ -101,7 +104,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; @@ -123,12 +126,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/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/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 f89f833072a3..59339d79c35b 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,16 @@ ```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'; // @public export interface ContainerSchema { @@ -48,30 +44,6 @@ export type DataObjectClass = { readonly factory: IFluidDataStoreFactory; } & LoadableObjectCtor; -// @public @deprecated -export class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { - constructor(schema: ContainerSchema); - // (undocumented) - protected containerInitializingFirstTime(runtime: IContainerRuntime): Promise; -} - -// @public @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; - // @internal - readonly INTERNAL_CONTAINER_DO_NOT_USE?: () => IContainer; - get isDirty(): boolean; -} - // @public export interface IConnection { id: string; @@ -154,19 +126,6 @@ export type Myself = M & { currentConnection: string; }; -// @public @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; -} - // @public export type SharedObjectClass = { readonly getFactory: () => IChannelFactory; diff --git a/packages/framework/fluid-static/package.json b/packages/framework/fluid-static/package.json index c88b8ed0e639..a4ecbffebc11 100644 --- a/packages/framework/fluid-static/package.json +++ b/packages/framework/fluid-static/package.json @@ -124,6 +124,19 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "RemovedClassDeclaration_DOProviderContainerRuntimeFactory": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_FluidContainer": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedClassDeclaration_ServiceAudience": { + "forwardCompat": false, + "backCompat": false + } + } } } diff --git a/packages/framework/fluid-static/src/fluidContainer.ts b/packages/framework/fluid-static/src/fluidContainer.ts index 13f47e0a78d1..6ec3d9ca6c9c 100644 --- a/packages/framework/fluid-static/src/fluidContainer.ts +++ b/packages/framework/fluid-static/src/fluidContainer.ts @@ -225,9 +225,8 @@ export function createFluidContainer< * * Note: this implementation is not complete. Consumers who rely on {@link IFluidContainer.attach} * will need to utilize or provide a service-specific implementation of this type that implements that method. - * @deprecated use {@link createFluidContainer} and {@link IFluidContainer} instead */ -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 f2b96e6c3e78..ce05f8dc8bdf 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 e885511e791d..a772dbf5030c 100644 --- a/packages/framework/fluid-static/src/rootDataObject.ts +++ b/packages/framework/fluid-static/src/rootDataObject.ts @@ -153,9 +153,8 @@ 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 */ -export class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { +class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { private readonly rootDataObjectFactory: DataObjectFactory< RootDataObject, { diff --git a/packages/framework/fluid-static/src/serviceAudience.ts b/packages/framework/fluid-static/src/serviceAudience.ts index 1a152f9b4ddb..a719a1952b7e 100644 --- a/packages/framework/fluid-static/src/serviceAudience.ts +++ b/packages/framework/fluid-static/src/serviceAudience.ts @@ -12,13 +12,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); } /** @@ -30,16 +24,15 @@ 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 */ -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. @@ -58,13 +51,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; @@ -91,13 +85,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} */ @@ -166,7 +153,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..03eaeb6285c5 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 @@ -432,26 +408,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/tinylicious-client/api-report/tinylicious-client.api.md b/packages/framework/tinylicious-client/api-report/tinylicious-client.api.md index 7dc904e07d19..bdb17a896635 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 } // @public export type ITinyliciousAudience = IServiceAudience; -// @public @deprecated -export class TinyliciousAudience extends ServiceAudience implements ITinyliciousAudience { - // @internal (undocumented) - protected createServiceMember(audienceMember: IClient): TinyliciousMember; -} - // @public class TinyliciousClient { constructor(props?: TinyliciousClientProps | undefined); diff --git a/packages/framework/tinylicious-client/package.json b/packages/framework/tinylicious-client/package.json index f2970ba28928..d8071ff955ab 100644 --- a/packages/framework/tinylicious-client/package.json +++ b/packages/framework/tinylicious-client/package.json @@ -105,6 +105,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 f022cab3fbc9..fe92f445fac3 100644 --- a/packages/framework/tinylicious-client/src/TinyliciousAudience.ts +++ b/packages/framework/tinylicious-client/src/TinyliciousAudience.ts @@ -4,27 +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} - * - * @public - * @deprecated use {@link ITinyliciousAudience} instead - */ -export class TinyliciousAudience - extends ServiceAudience - implements ITinyliciousAudience -{ - /** - * @internal - */ - 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/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 From 5e77cfe453183ae55cce82ad16c8326593e13d88 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Tue, 28 Nov 2023 10:21:39 -0800 Subject: [PATCH 33/50] Remove duplicate createSummarizer related methods (#18449) We have a bunch of `createSummarizer` type methods doing essentially the same thing. We should unify the usages under as few places as possible. Note: the ODSP e2e tests for single commit summary are currently broken, so I couldn't verify if this change breaks anything --- .../api-report/container-runtime.api.md | 6 --- .../runtime/container-runtime/package.json | 4 ++ .../container-runtime/src/containerRuntime.ts | 8 --- .../runtime/container-runtime/src/index.ts | 1 - .../src/summary/summarizer.ts | 51 +------------------ ...idateContainerRuntimePrevious.generated.ts | 16 +----- .../src/test/loadModes.spec.ts | 46 ++++------------- .../src/test/summaries.spec.ts | 48 +++++++---------- 8 files changed, 36 insertions(+), 144 deletions(-) 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 14899af83171..a49d2f982e29 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -31,7 +31,6 @@ 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'; @@ -761,8 +760,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; @@ -845,9 +842,6 @@ export class SummaryCollection extends TypedEventEmitter; - // @public export const TombstoneResponseHeaderKey = "isTombstoned"; diff --git a/packages/runtime/container-runtime/package.json b/packages/runtime/container-runtime/package.json index 1405d21af36b..ffa3d914e73e 100644 --- a/packages/runtime/container-runtime/package.json +++ b/packages/runtime/container-runtime/package.json @@ -128,6 +128,10 @@ "broken": { "ClassDeclaration_ContainerRuntime": { "backCompat": false + }, + "RemovedFunctionDeclaration_TEST_requestSummarizer": { + "forwardCompat": false, + "backCompat": false } } } diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 04bb2ec6d8fb..c55e8293895d 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -707,14 +707,6 @@ 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. diff --git a/packages/runtime/container-runtime/src/index.ts b/packages/runtime/container-runtime/src/index.ts index 20aa7db5df54..b7383a99f04b 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/summary/summarizer.ts b/packages/runtime/container-runtime/src/summary/summarizer.ts index 3cbc9d0fa130..1bb4b5a0fbbe 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/types/validateContainerRuntimePrevious.generated.ts b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts index e963854df492..a70ad6fafe37 100644 --- a/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts +++ b/packages/runtime/container-runtime/src/test/types/validateContainerRuntimePrevious.generated.ts @@ -1873,26 +1873,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/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 6de270aed921..ff2d95fe5d8c 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 { describeNoCompat } 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 @@ describeNoCompat("LoadModes", (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 @@ describeNoCompat("LoadModes", (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 @@ describeNoCompat("LoadModes", (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/summaries.spec.ts b/packages/test/test-end-to-end-tests/src/test/summaries.spec.ts index 5f105e5262a6..fe39b0e7534c 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 @@ describeNoCompat("SingleCommit Summaries Tests", (getTestObjectProvider) => { 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 @@ describeNoCompat("SingleCommit Summaries Tests", (getTestObjectProvider) => { 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 @@ describeNoCompat("SingleCommit Summaries Tests", (getTestObjectProvider) => { ); summarizer.close(); - const summarizer2 = await createSummarizerFromContainer( + const { summarizer: summarizer2 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, @@ -676,7 +664,7 @@ describeNoCompat("SingleCommit Summaries Tests", (getTestObjectProvider) => { await flushPromises(); // Create new summarizer - const summarizer2 = await createSummarizerFromContainer( + const { summarizer: summarizer2 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, @@ -701,7 +689,7 @@ describeNoCompat("SingleCommit Summaries Tests", (getTestObjectProvider) => { await flushPromises(); // Create new summarizer - const summarizer3 = await createSummarizerFromContainer( + const { summarizer: summarizer3 } = await createSummarizer( provider, mainContainer, configForSingleCommitSummary, From a505c4551247aeef4a28b76d8d636efd32f20299 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:13:58 -0800 Subject: [PATCH 34/50] Remove request pattern from Loader and Container (#18520) ## Breaking Changes ### 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. ### 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. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/bitter-clocks-sleep.md | 9 + .changeset/floppy-mails-hammer.md | 10 + .../api-report/container-definitions.api.md | 16 +- .../common/container-definitions/package.json | 15 + .../container-definitions/src/loader.ts | 57 +--- .../test/types/requestSignatureDeprecation.ts | 19 -- ...eContainerDefinitionsPrevious.generated.ts | 5 + .../core-interfaces/Removing-IFluidRouter.md | 8 +- .../api-report/container-loader.api.md | 6 - packages/loader/container-loader/package.json | 6 + .../loader/container-loader/src/container.ts | 23 -- .../loader/container-loader/src/loader.ts | 61 +--- ...lidateContainerLoaderPrevious.generated.ts | 2 + .../container-runtime/src/containerRuntime.ts | 4 +- .../runtime/test-runtime-utils/package.json | 6 +- ...idateTestRuntimeUtilsPrevious.generated.ts | 1 + .../src/test/entryPointCompat.spec.ts | 27 +- .../src/test/loaderTest.spec.ts | 309 ------------------ .../tools/devtools/devtools-core/package.json | 9 +- .../validateDevtoolsCorePrevious.generated.ts | 2 + .../tools/webpack-fluid-loader/src/loader.ts | 13 +- 21 files changed, 77 insertions(+), 531 deletions(-) create mode 100644 .changeset/bitter-clocks-sleep.md create mode 100644 .changeset/floppy-mails-hammer.md delete mode 100644 packages/common/container-definitions/src/test/types/requestSignatureDeprecation.ts delete mode 100644 packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts 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/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/packages/common/container-definitions/api-report/container-definitions.api.md b/packages/common/container-definitions/api-report/container-definitions.api.md index 0c10cf852415..9abd81b940e7 100644 --- a/packages/common/container-definitions/api-report/container-definitions.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.api.md @@ -17,7 +17,6 @@ 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'; @@ -124,7 +123,7 @@ export interface IConnectionDetails { } // @public -export interface IContainer extends IEventProvider, IFluidRouter { +export interface IContainer extends IEventProvider { attach(request: IRequest, attachProps?: { deltaConnection?: "none" | "delayed"; }): Promise; @@ -146,18 +145,9 @@ export interface IContainer extends IEventProvider, IFluidRout 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; } @@ -403,10 +393,6 @@ export interface IHostLoader extends ILoader { // @public export interface ILoader extends Partial { - // @deprecated (undocumented) - readonly IFluidRouter: IFluidRouter; - // @deprecated (undocumented) - request(request: IRequest): Promise; resolve(request: IRequest, pendingLocalState?: string): Promise; } diff --git a/packages/common/container-definitions/package.json b/packages/common/container-definitions/package.json index 228c32da338e..f1db677d5fad 100644 --- a/packages/common/container-definitions/package.json +++ b/packages/common/container-definitions/package.json @@ -85,6 +85,21 @@ }, "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/loader.ts b/packages/common/container-definitions/src/loader.ts index b54f6368d0aa..8521b49c3be4 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, @@ -301,7 +293,7 @@ export type ConnectionState = * @public */ // 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 */ @@ -403,40 +395,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. * @@ -523,17 +481,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; } /** 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 4937fd6cb992..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()); /* @@ -1111,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()); /* diff --git a/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 9d147a31ca7f..b0384c421440 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -97,11 +97,11 @@ const entryPoint = await container.getEntryPoint(); | API | Deprecated in | Removed in | | -------------------------------------------------------------------------------------------- | -------------------- | -------------------- | -| `IContainer.request` (except calling with "/") | 2.0.0-internal.6.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 | | -| `IContainer.IFluidRouter` | 2.0.0-internal.6.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 | | -| `request` and `IFluidRouter` on `ILoader` and `Loader` | 2.0.0-internal.6.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 | | | `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 | | @@ -110,7 +110,7 @@ const entryPoint = await container.getEntryPoint(); | `IFluidHandleContext` on `IContainerRuntimeBase` | 2.0.0-internal.7.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 `IContainer` and `Container` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.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 | | 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 8d0bdd4a0137..219b8e3eac96 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,7 +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 { IResponse } from '@fluidframework/core-interfaces'; import { ISignalMessage } from '@fluidframework/protocol-definitions'; import { ITelemetryBaseLogger } from '@fluidframework/core-interfaces'; import { ITelemetryLoggerExt } from '@fluidframework/telemetry-utils'; @@ -117,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) diff --git a/packages/loader/container-loader/package.json b/packages/loader/container-loader/package.json index b46bc2528186..131f7d6a865e 100644 --- a/packages/loader/container-loader/package.json +++ b/packages/loader/container-loader/package.json @@ -125,6 +125,12 @@ "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 eda1c769b5c0..aaf1bb5e44c3 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"; @@ -599,14 +596,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, @@ -1346,18 +1335,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/loader.ts b/packages/loader/container-loader/src/loader.ts index 93e17256e6f3..f1ae8259edf1 100644 --- a/packages/loader/container-loader/src/loader.ts +++ b/packages/loader/container-loader/src/loader.ts @@ -14,14 +14,7 @@ import { createChildMonitoringContext, UsageError, } from "@fluidframework/telemetry-utils"; -import { - ITelemetryBaseLogger, - FluidObject, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, - IRequest, - IResponse, -} from "@fluidframework/core-interfaces"; +import { ITelemetryBaseLogger, FluidObject, IRequest } from "@fluidframework/core-interfaces"; import { IContainer, IFluidModule, @@ -61,14 +54,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); @@ -91,25 +76,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); - } } export interface ILoaderOptions extends ILoaderOptions1 { @@ -318,14 +284,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?: { @@ -369,23 +327,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/test/types/validateContainerLoaderPrevious.generated.ts b/packages/loader/container-loader/src/test/types/validateContainerLoaderPrevious.generated.ts index f7e25ce4d01a..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()); /* diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index c55e8293895d..47c4d574b065 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -694,7 +694,9 @@ async function createSummarizer(loader: ILoader, url: string): Promise): void; use_old_ClassDeclaration_MockFluidDataStoreRuntime( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_MockFluidDataStoreRuntime()); /* 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 5909e522ac96..5be1f66acc4e 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/loaderTest.spec.ts b/packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts deleted file mode 100644 index 0a02ffa37f00..000000000000 --- a/packages/test/test-end-to-end-tests/src/test/loaderTest.spec.ts +++ /dev/null @@ -1,309 +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 { - describeNoCompat, - itSkipsFailureOnSpecificDrivers, -} from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; -import { RuntimeHeaders } from "@fluidframework/container-runtime"; -import { requestFluidObject } from "@fluidframework/runtime-utils"; - -// REVIEW: enable compat testing? -// TODO: Remove this file when request is removed from Loader AB#4991 -describeNoCompat("Loader.request", (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", - ); - }); - - // 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/tools/devtools/devtools-core/package.json b/packages/tools/devtools/devtools-core/package.json index 98ade443f4ff..7b577db52557 100644 --- a/packages/tools/devtools/devtools-core/package.json +++ b/packages/tools/devtools/devtools-core/package.json @@ -126,6 +126,13 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "InterfaceDeclaration_ContainerDevtoolsProps": { + "backCompat": false + }, + "InterfaceDeclaration_FluidDevtoolsProps": { + "backCompat": false + } + } } } 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..55dc38756df7 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()); /* 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 "//" From 72c29c63205147dfb308ed5653538d2dc6fb287a Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:28:26 -0800 Subject: [PATCH 35/50] Remove requestFluidObject (#18534) ## Breaking Changes ### 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. [AB#5498](https://dev.azure.com/fluidframework/internal/_workitems/edit/5498) --- .changeset/shaky-socks-fail.md | 9 ++++++++ .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../api-report/runtime-utils.api.md | 5 ----- packages/runtime/runtime-utils/package.json | 7 +++++- .../runtime-utils/src/dataStoreHelpers.ts | 22 +------------------ packages/runtime/runtime-utils/src/index.ts | 1 - .../validateRuntimeUtilsPrevious.generated.ts | 16 ++------------ packages/test/test-utils/README.md | 6 ++--- 8 files changed, 22 insertions(+), 46 deletions(-) create mode 100644 .changeset/shaky-socks-fail.md 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index b0384c421440..7ecabd152deb 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -113,7 +113,7 @@ const entryPoint = await container.getEntryPoint(); | `request` and `IFluidRouter` on `IContainer` and `Container` | 2.0.0-internal.7.0.0 | 2.0.0-internal.8.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 | | +| `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 | 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 dc5f1dc2e798..27605b115c79 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'; @@ -122,9 +120,6 @@ export class ObjectStoragePartition implements IChannelStorageService { // @public export type ReadAndParseBlob = (id: string) => Promise; -// @public @deprecated (undocumented) -export function requestFluidObject(router: IFluidRouter, url: string | IRequest): Promise; - // @public 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 9670d592672d..fd9860e166ce 100644 --- a/packages/runtime/runtime-utils/package.json +++ b/packages/runtime/runtime-utils/package.json @@ -110,6 +110,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 63a6316ba422..f3fd80953335 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, @@ -66,25 +65,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 - */ -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; -} - export const create404Response = (request: IRequest) => createResponseError(404, "not found", request); 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/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/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. From d9087caf86b2dc96cc2fda5860ac524b9270eb39 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:53:44 -0800 Subject: [PATCH 36/50] Remove request from ContainerRuntime (#18542) ### Breaking Changes ## 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. [AB#6149](https://dev.azure.com/fluidframework/internal/_workitems/edit/6149) --- .changeset/lucky-worms-return.md | 17 +++++++++++++++++ .../api-report/container-definitions.api.md | 3 --- .../common/container-definitions/src/runtime.ts | 14 +------------- .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../container-runtime-definitions/package.json | 12 +++++++++++- ...ainerRuntimeDefinitionsPrevious.generated.ts | 3 +++ .../api-report/container-runtime.api.md | 4 ---- .../container-runtime/src/containerRuntime.ts | 12 +++--------- .../src/test/containerRuntime.spec.ts | 4 +++- .../api-report/runtime-definitions.api.md | 2 -- .../runtime/runtime-definitions/package.json | 9 +++++++++ .../runtime-definitions/src/dataStoreContext.ts | 6 ------ ...idateRuntimeDefinitionsPrevious.generated.ts | 3 +++ .../runtime/test-runtime-utils/package.json | 3 +++ ...alidateTestRuntimeUtilsPrevious.generated.ts | 1 + 15 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 .changeset/lucky-worms-return.md 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/packages/common/container-definitions/api-report/container-definitions.api.md b/packages/common/container-definitions/api-report/container-definitions.api.md index 9abd81b940e7..ef28fb59f1b9 100644 --- a/packages/common/container-definitions/api-report/container-definitions.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.api.md @@ -21,7 +21,6 @@ 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'; @@ -453,8 +452,6 @@ export interface IRuntime extends IDisposable { 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; } diff --git a/packages/common/container-definitions/src/runtime.ts b/packages/common/container-definitions/src/runtime.ts index bfc1d359a94a..480c735d3bbe 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 { @@ -59,12 +53,6 @@ export enum AttachState { * @public */ 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 */ diff --git a/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 7ecabd152deb..739091c72fe2 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -102,7 +102,7 @@ const entryPoint = await container.getEntryPoint(); | `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 | | | `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 | | +| `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` 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 | | diff --git a/packages/runtime/container-runtime-definitions/package.json b/packages/runtime/container-runtime-definitions/package.json index ce07be68b523..eddd4173cb27 100644 --- a/packages/runtime/container-runtime-definitions/package.json +++ b/packages/runtime/container-runtime-definitions/package.json @@ -67,6 +67,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/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 a49d2f982e29..eca93168959a 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -165,8 +165,6 @@ export class ContainerRuntime extends TypedEventEmitter; - // @deprecated - request(request: IRequest): Promise; resolveHandle(request: IRequest): Promise; // (undocumented) get scope(): FluidObject; diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 47c4d574b065..e97e60a8be5c 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -723,13 +723,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; - } - /** * Load the stores from a snapshot and returns the runtime. * @param params - An object housing the runtime properties: @@ -1733,9 +1726,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]; diff --git a/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts b/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts index d305e6634f7e..8610c927e8c7 100644 --- a/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts +++ b/packages/runtime/container-runtime/src/test/containerRuntime.spec.ts @@ -1262,7 +1262,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/runtime-definitions/api-report/runtime-definitions.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md index e632bfbbc74f..ea5015427902 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -131,8 +131,6 @@ export interface IContainerRuntimeBase extends IEventProvider void): void; - // @deprecated - request(request: IRequest): Promise; submitSignal(type: string, content: any): void; // (undocumented) uploadBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise>; diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index ed2d6add9c1b..00c872c461b6 100644 --- a/packages/runtime/runtime-definitions/package.json +++ b/packages/runtime/runtime-definitions/package.json @@ -86,6 +86,15 @@ "RemovedVariableDeclaration_initialClusterCapacity": { "forwardCompat": false, "backCompat": false + }, + "InterfaceDeclaration_IContainerRuntimeBase": { + "backCompat": false + }, + "InterfaceDeclaration_IFluidDataStoreContext": { + "backCompat": false + }, + "InterfaceDeclaration_IFluidDataStoreContextDetached": { + "backCompat": false } } } diff --git a/packages/runtime/runtime-definitions/src/dataStoreContext.ts b/packages/runtime/runtime-definitions/src/dataStoreContext.ts index 09a73b445e9f..2d6cb9c72cec 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -195,12 +195,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. 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 43718b199a24..7f64dd0ccff4 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()); /* @@ -451,6 +452,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 +477,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()); /* diff --git a/packages/runtime/test-runtime-utils/package.json b/packages/runtime/test-runtime-utils/package.json index 4a6ddd35680b..1d8ea42be3d0 100644 --- a/packages/runtime/test-runtime-utils/package.json +++ b/packages/runtime/test-runtime-utils/package.json @@ -115,6 +115,9 @@ "broken": { "ClassDeclaration_MockFluidDataStoreRuntime": { "backCompat": false + }, + "ClassDeclaration_MockFluidDataStoreContext": { + "backCompat": false } } } 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 b056b516c0d9..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()); /* From 249a6fe8a12b7c18c0db7cca2c244ad2c47f7ce9 Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Thu, 30 Nov 2023 09:54:37 -0800 Subject: [PATCH 37/50] feat(merge-tree): minor obliterate fixes (#18540) Resolves a few pieces of pending obliterate functionality that didn't make it into the main PR. There are still some obliterate issues, but these are the lowest-hanging and most important to get into the next release. --- packages/dds/merge-tree/src/mergeTree.ts | 8 +++++--- packages/dds/merge-tree/src/zamboni.ts | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/dds/merge-tree/src/mergeTree.ts b/packages/dds/merge-tree/src/mergeTree.ts index 64ab58478baf..92d97f449e53 100644 --- a/packages/dds/merge-tree/src/mergeTree.ts +++ b/packages/dds/merge-tree/src/mergeTree.ts @@ -387,8 +387,10 @@ function getSlideToSegment( result.seg = seg; return false; } - // TODO: ADO#3715 moveSeq should be taken into account here - if (cache !== undefined && seg.removedSeq === segment.removedSeq) { + if ( + cache !== undefined && + (seg.removedSeq === segment.removedSeq || seg.movedSeq === segment.movedSeq) + ) { cache.set(seg, result); } return true; @@ -2477,7 +2479,7 @@ export class MergeTree { _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", ); } diff --git a/packages/dds/merge-tree/src/zamboni.ts b/packages/dds/merge-tree/src/zamboni.ts index 2c04ced66c17..6abe20e22968 100644 --- a/packages/dds/merge-tree/src/zamboni.ts +++ b/packages/dds/merge-tree/src/zamboni.ts @@ -15,6 +15,7 @@ import { Marker, MaxNodesInBlock, seqLTE, + toMoveInfo, toRemovalInfo, } from "./mergeTreeNodes"; import { matchProperties } from "./properties"; @@ -140,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?.( From c80e17141f71a02baaa13008853a69a217ef88ae Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:34:16 -0800 Subject: [PATCH 38/50] Remove requestHandler utilities (#18546) ## Breaking Changes ### Removed `requestHandler` utilities The following `requestHandler` utilities have been removed: - `makeModelRequestHandler` - `defaultFluidObjectRequestHandler` - `defaultRouteRequestHandler` - `mountableViewRequestHandler` - `createFluidObjectResponse` - `rootDataStoreRequestHandler` - `handleFromLegacyUri` 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. ## Reviewer Guidance The removal of the following items will need to wait for the LTS version of the `Loader` to reach "2.0.0-internal.7.0.0": - `requestHandler` property in `ContainerRuntime.loadRuntime(...)`, `BaseContainerRuntimeFactory`, `ContainerRuntimeFactoryWithDefaultDataStore`, `mixinAttributor`, `RuntimeFactory`, `TestContainerRuntimeFactory` - `RuntimeRequestHandler` and `RuntimeRequestHandlerBuilder` [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/thick-ads-marry.md | 22 ++++ .../data-objects/table-document/src/slice.ts | 12 +- .../api-report/example-utils.api.md | 7 -- examples/utils/example-utils/src/index.ts | 2 - .../example-utils/src/modelLoader/index.ts | 4 +- .../src/modelLoader/interfaces.ts | 11 -- .../src/modelLoader/modelLoader.ts | 40 +------ .../core-interfaces/Removing-IFluidRouter.md | 5 + .../aqueduct/api-report/aqueduct.api.md | 11 -- packages/framework/aqueduct/package.json | 12 ++ .../baseContainerRuntimeFactory.ts | 2 +- ...ainerRuntimeFactoryWithDefaultDataStore.ts | 24 ++-- packages/framework/aqueduct/src/index.ts | 5 - .../aqueduct/src/request-handlers/index.ts | 10 -- .../src/request-handlers/requestHandlers.ts | 102 ----------------- .../aqueduct/src/test/aqueduct.spec.ts | 7 ++ .../aqueduct/src/test/defaultRoute.spec.ts | 89 --------------- .../validateAqueductPrevious.generated.ts | 48 +------- .../attributor/src/mixinAttributor.ts | 2 +- .../data-object-base/src/runtimeFactory.ts | 2 +- packages/framework/fluid-static/package.json | 1 + .../fluid-static/src/rootDataObject.ts | 37 ++++--- .../api-report/request-handler.api.md | 27 +---- .../framework/request-handler/package.json | 19 +++- .../framework/request-handler/src/index.ts | 12 +- .../request-handler/src/requestHandlers.ts | 100 +---------------- .../src/runtimeRequestHandlerBuilder.ts | 10 +- .../src/test/requestHandler.spec.ts | 7 ++ .../src/test/requestHandlers.spec.ts | 103 ------------------ ...alidateRequestHandlerPrevious.generated.ts | 64 ++--------- .../container-runtime/src/containerRuntime.ts | 2 +- .../src/test/noDeltaStream.spec.ts | 2 - .../src/test/agentScheduler.spec.ts | 2 - .../src/testContainerRuntimeFactory.ts | 19 +++- .../test/test-utils/src/testFluidObject.ts | 8 +- pnpm-lock.yaml | 2 + 36 files changed, 170 insertions(+), 662 deletions(-) create mode 100644 .changeset/thick-ads-marry.md delete mode 100644 packages/framework/aqueduct/src/request-handlers/index.ts delete mode 100644 packages/framework/aqueduct/src/request-handlers/requestHandlers.ts create mode 100644 packages/framework/aqueduct/src/test/aqueduct.spec.ts delete mode 100644 packages/framework/aqueduct/src/test/defaultRoute.spec.ts create mode 100644 packages/framework/request-handler/src/test/requestHandler.spec.ts delete mode 100644 packages/framework/request-handler/src/test/requestHandlers.spec.ts 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/examples/data-objects/table-document/src/slice.ts b/examples/data-objects/table-document/src/slice.ts index 07d59f96681b..a9a948fc151e 100644 --- a/examples/data-objects/table-document/src/slice.ts +++ b/examples/data-objects/table-document/src/slice.ts @@ -6,7 +6,6 @@ import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct"; import { IFluidHandle } from "@fluidframework/core-interfaces"; import { PropertySet } from "@fluidframework/sequence"; -import { handleFromLegacyUri } from "@fluidframework/request-handler"; import { CellRange } from "./cellrange"; import { TableSliceType } from "./componentTypes"; import { ConfigKey } from "./configKey"; @@ -122,10 +121,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/utils/example-utils/api-report/example-utils.api.md b/examples/utils/example-utils/api-report/example-utils.api.md index 255360f51048..2525d30ed491 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; } -// @public @deprecated -export const makeModelRequestHandler: (modelMakerCallback: ModelMakerCallback) => (request: IRequest, runtime: IContainerRuntime) => Promise; - // @public export type MigrationState = "collaborating" | "stopping" | "migrating" | "migrated"; @@ -249,9 +245,6 @@ export class ModelLoader implements IModelLoader { supportsVersion(version: string): Promise; } -// @public -export type ModelMakerCallback = (runtime: IContainerRuntime, container: IContainer) => Promise; - // @public export type SameContainerMigrationState = "collaborating" | "proposingMigration" | "stoppingCollaboration" | "proposingV2Code" | "waitingForV2ProposalCompletion" | "readyForMigration" | "uploadingV2Summary" | "submittingV2Summary" | "migrated"; 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 abece5f34d20..d98d60479a0d 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(). */ @@ -52,11 +49,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. - */ -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 c60c84d0a1d7..8e648f8dfd71 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,40 +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 - */ -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); - }; -}; - export class ModelLoader implements IModelLoader { private readonly loader: IHostLoader; private readonly generateCreateNewRequest: () => IRequest; diff --git a/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 739091c72fe2..6e3d1b61bd2c 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -117,3 +117,8 @@ const entryPoint = await container.getEntryPoint(); | `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/framework/aqueduct/api-report/aqueduct.api.md b/packages/framework/aqueduct/api-report/aqueduct.api.md index 9e3b27952b97..f43c1e2e76da 100644 --- a/packages/framework/aqueduct/api-report/aqueduct.api.md +++ b/packages/framework/aqueduct/api-report/aqueduct.api.md @@ -23,7 +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'; @@ -32,7 +31,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'; @@ -94,12 +92,6 @@ export interface DataObjectTypes { OptionalProviders?: FluidObject; } -// @public @deprecated -export function defaultFluidObjectRequestHandler(fluidObject: FluidObject, request: IRequest): IResponse; - -// @public @deprecated -export const defaultRouteRequestHandler: (defaultRootId: string) => (request: IRequest, runtime: IContainerRuntime) => Promise; - // @public (undocumented) export interface IDataObjectProps { // (undocumented) @@ -118,9 +110,6 @@ export interface IRootDataObjectFactory extends IFluidDataStoreFactory { createRootInstance(rootDataStoreId: string, runtime: IContainerRuntime): Promise; } -// @public @deprecated -export const mountableViewRequestHandler: (MountableViewClass: IFluidMountableViewClass, handlers: RuntimeRequestHandler[]) => (request: RequestParser, runtime: IContainerRuntime) => Promise; - // @public export abstract class PureDataObject extends TypedEventEmitter implements IFluidLoadable, IFluidRouter, IProvideFluidHandle { constructor(props: IDataObjectProps); diff --git a/packages/framework/aqueduct/package.json b/packages/framework/aqueduct/package.json index 5f6fd1065863..2855c9101b77 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -144,6 +144,18 @@ "RemovedFunctionDeclaration_getObjectWithIdFromContainer": { "forwardCompat": false, "backCompat": false + }, + "RemovedFunctionDeclaration_defaultFluidObjectRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_defaultRouteRequestHandler": { + "forwardCompat": false, + "backCompat": false + }, + "RemovedVariableDeclaration_mountableViewRequestHandler": { + "forwardCompat": false, + "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 27e5f74fe2dd..bc2e08a0000a 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 286bdd4d4054..99b75bf9bd56 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/index.ts b/packages/framework/aqueduct/src/index.ts index 77611f9e24eb..da485943652d 100644 --- a/packages/framework/aqueduct/src/index.ts +++ b/packages/framework/aqueduct/src/index.ts @@ -28,8 +28,3 @@ export { BaseContainerRuntimeFactory, ContainerRuntimeFactoryWithDefaultDataStore, } from "./container-runtime-factories"; -export { - defaultFluidObjectRequestHandler, - defaultRouteRequestHandler, - mountableViewRequestHandler, -} from "./request-handlers"; diff --git a/packages/framework/aqueduct/src/request-handlers/index.ts b/packages/framework/aqueduct/src/request-handlers/index.ts deleted file mode 100644 index 8c4409234d72..000000000000 --- a/packages/framework/aqueduct/src/request-handlers/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -export { - defaultFluidObjectRequestHandler, - defaultRouteRequestHandler, - mountableViewRequestHandler, -} from "./requestHandlers"; diff --git a/packages/framework/aqueduct/src/request-handlers/requestHandlers.ts b/packages/framework/aqueduct/src/request-handlers/requestHandlers.ts deleted file mode 100644 index 0a5e9a0f7e57..000000000000 --- a/packages/framework/aqueduct/src/request-handlers/requestHandlers.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright (c) Microsoft Corporation and contributors. All rights reserved. - * Licensed under the MIT License. - */ - -import { FluidObject, IRequest, IRequestHeader, IResponse } from "@fluidframework/core-interfaces"; -import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; -import type { IFluidMountableViewClass } from "@fluidframework/view-interfaces"; -// eslint-disable-next-line import/no-deprecated -import { RuntimeRequestHandler, buildRuntimeRequestHandler } from "@fluidframework/request-handler"; -import { RequestParser, create404Response } from "@fluidframework/runtime-utils"; - -/** - * A mountable view is only required if the view needs to be mounted across a bundle boundary. Mounting across - * bundle boundaries breaks some frameworks, so the mountable view is used to ensure the mounting is done within - * the same bundle as the view. For example, React hooks don't work if mounted across bundles since there will - * be two React instances, breaking the Rules of Hooks. When cross-bundle mounting isn't required, the mountable - * view isn't necessary. - * - * When a request is received with a mountableView: true header, this request handler will reissue the request - * without the header, and respond with a mountable view of the given class using the response. - * @param MountableViewClass - The type of mountable view to use when responding - * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md - * @public - */ -export const mountableViewRequestHandler = ( - MountableViewClass: IFluidMountableViewClass, - handlers: RuntimeRequestHandler[], -) => { - // 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 - * @public - */ -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 - * @public - */ -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/packages/framework/aqueduct/src/test/aqueduct.spec.ts b/packages/framework/aqueduct/src/test/aqueduct.spec.ts new file mode 100644 index 000000000000..72d47def029f --- /dev/null +++ b/packages/framework/aqueduct/src/test/aqueduct.spec.ts @@ -0,0 +1,7 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +// 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 5c8c40b96317..f02cff3fb303 100644 --- a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts +++ b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts @@ -240,50 +240,26 @@ 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 @@ -324,23 +300,11 @@ use_old_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: -* "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/attributor/src/mixinAttributor.ts b/packages/framework/attributor/src/mixinAttributor.ts index 557c3f220b14..f7f755a225ff 100644 --- a/packages/framework/attributor/src/mixinAttributor.ts +++ b/packages/framework/attributor/src/mixinAttributor.ts @@ -110,7 +110,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/data-object-base/src/runtimeFactory.ts b/packages/framework/data-object-base/src/runtimeFactory.ts index b7199a0494b1..981feacfde59 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/fluid-static/package.json b/packages/framework/fluid-static/package.json index a4ecbffebc11..dd0e4df09fe4 100644 --- a/packages/framework/fluid-static/package.json +++ b/packages/framework/fluid-static/package.json @@ -78,6 +78,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:~", diff --git a/packages/framework/fluid-static/src/rootDataObject.ts b/packages/framework/fluid-static/src/rootDataObject.ts index c4658a272fe3..876e302ca57f 100644 --- a/packages/framework/fluid-static/src/rootDataObject.ts +++ b/packages/framework/fluid-static/src/rootDataObject.ts @@ -6,13 +6,13 @@ 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, @@ -176,21 +176,32 @@ class DOProviderContainerRuntimeFactory extends BaseContainerRuntimeFactory { {}, 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/request-handler/api-report/request-handler.api.md b/packages/framework/request-handler/api-report/request-handler.api.md index 6b170a79d31f..260402e20dee 100644 --- a/packages/framework/request-handler/api-report/request-handler.api.md +++ b/packages/framework/request-handler/api-report/request-handler.api.md @@ -4,42 +4,17 @@ ```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'; -// @public @deprecated (undocumented) +// @internal @deprecated (undocumented) export function buildRuntimeRequestHandler(...handlers: RuntimeRequestHandler[]): (request: IRequest, runtime: IContainerRuntime) => Promise; -// @public @deprecated (undocumented) -export const createFluidObjectResponse: (fluidObject: FluidObject) => { - status: 200; - mimeType: "fluid/object"; - value: FluidObject; -}; - -// @public @deprecated (undocumented) -export function handleFromLegacyUri(uri: string, runtime: IContainerRuntimeBase): IFluidHandle; - -// @public @deprecated -export const rootDataStoreRequestHandler: (request: IRequest, runtime: IContainerRuntime) => Promise; - // @public @deprecated export type RuntimeRequestHandler = (request: RequestParser, runtime: IContainerRuntime) => Promise; -// @public @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 952cf59b0b32..94f6da789153 100644 --- a/packages/framework/request-handler/package.json +++ b/packages/framework/request-handler/package.json @@ -119,6 +119,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 faf557e61d87..184993ed91c9 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,7 +12,7 @@ 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 * * @public */ @@ -30,89 +20,3 @@ 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 - * - * @public - */ -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 - * - * @public - */ -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 - * - * @public - */ -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 d8bf44fbd968..8a3ad2fd3e32 100644 --- a/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts +++ b/packages/framework/request-handler/src/runtimeRequestHandlerBuilder.ts @@ -12,12 +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 - * - * @public */ -export class RuntimeRequestHandlerBuilder { +class RuntimeRequestHandlerBuilder { // eslint-disable-next-line import/no-deprecated private readonly handlers: RuntimeRequestHandler[] = []; @@ -41,9 +37,9 @@ 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 * - * @public + * @internal */ // eslint-disable-next-line import/no-deprecated export function buildRuntimeRequestHandler(...handlers: RuntimeRequestHandler[]) { diff --git a/packages/framework/request-handler/src/test/requestHandler.spec.ts b/packages/framework/request-handler/src/test/requestHandler.spec.ts new file mode 100644 index 000000000000..def818334195 --- /dev/null +++ b/packages/framework/request-handler/src/test/requestHandler.spec.ts @@ -0,0 +1,7 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +// 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/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index e97e60a8be5c..c8d3aac318b2 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -745,7 +745,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 { 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/test-end-to-end-tests/src/test/agentScheduler.spec.ts b/packages/test/test-end-to-end-tests/src/test/agentScheduler.spec.ts index 4996c1399531..6b95ea94c6e0 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 { describeFullCompat } 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 @@ describeFullCompat("AgentScheduler", (getTestObjectProvider, apis) => { AgentSchedulerFactory.type, new AgentSchedulerFactory(), {}, - [rootDataStoreRequestHandler], ), }; diff --git a/packages/test/test-utils/src/testContainerRuntimeFactory.ts b/packages/test/test-utils/src/testContainerRuntimeFactory.ts index ec4e18858f21..78b67f3e6bb9 100644 --- a/packages/test/test-utils/src/testContainerRuntimeFactory.ts +++ b/packages/test/test-utils/src/testContainerRuntimeFactory.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. */ -// eslint-disable-next-line import/no-deprecated -import { defaultRouteRequestHandler } from "@fluidframework/aqueduct"; import { IContainerContext, IRuntime } from "@fluidframework/container-definitions"; import { ContainerRuntime, @@ -12,10 +10,11 @@ import { DefaultSummaryConfiguration, } from "@fluidframework/container-runtime"; import { IContainerRuntime } from "@fluidframework/container-runtime-definitions"; +import { IRequest } 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 { RequestParser, RuntimeFactoryHelper } from "@fluidframework/runtime-utils"; /** * Create a container runtime factory class that allows you to set runtime options @@ -68,6 +67,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: [ @@ -76,8 +86,7 @@ export const createTestContainerRuntimeFactory = ( ], // eslint-disable-next-line import/no-deprecated requestHandler: buildRuntimeRequestHandler( - // eslint-disable-next-line import/no-deprecated - 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 0dac3fb1e93e..91087bd6b3ca 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"; /** @@ -89,8 +88,9 @@ export class TestFluidObject implements ITestFluidObject { * @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/pnpm-lock.yaml b/pnpm-lock.yaml index f6835fe3592c..57d080f938ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8422,6 +8422,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:~ @@ -8454,6 +8455,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 From 0777db45c5f127694e1fe6ebaf488f582e6ad0e7 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:04:23 -0800 Subject: [PATCH 39/50] Clean up typing around entryPoint (#18562) The following cleanup was done: - IRuntime.getEntryPoint() can no longer return undefined - make the `IRootDataObject` prop required for `IProvideRootDataObject` [AB#6341](https://dev.azure.com/fluidframework/internal/_workitems/edit/6341) --- azure/packages/azure-client/src/AzureClient.ts | 12 +++++------- .../api-report/container-definitions.api.md | 4 ++-- packages/common/container-definitions/src/loader.ts | 2 +- packages/common/container-definitions/src/runtime.ts | 2 +- .../fluid-static/api-report/fluid-static.api.md | 2 +- packages/framework/fluid-static/package.json | 3 +++ .../types/validateFluidStaticPrevious.generated.ts | 1 + packages/framework/fluid-static/src/types.ts | 2 +- .../tinylicious-client/src/TinyliciousClient.ts | 12 +++++------- packages/loader/container-loader/src/container.ts | 4 ++-- .../service-clients/odsp-client/src/odspClient.ts | 12 +++++------- 11 files changed, 27 insertions(+), 29 deletions(-) diff --git a/azure/packages/azure-client/src/AzureClient.ts b/azure/packages/azure-client/src/AzureClient.ts index b0f31c226938..98ad1d820464 100644 --- a/azure/packages/azure-client/src/AzureClient.ts +++ b/azure/packages/azure-client/src/AzureClient.ts @@ -307,13 +307,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/packages/common/container-definitions/api-report/container-definitions.api.md b/packages/common/container-definitions/api-report/container-definitions.api.md index ef28fb59f1b9..92915de336b5 100644 --- a/packages/common/container-definitions/api-report/container-definitions.api.md +++ b/packages/common/container-definitions/api-report/container-definitions.api.md @@ -140,7 +140,7 @@ export interface IContainer extends IEventProvider { // @alpha forceReadonly?(readonly: boolean): any; getAbsoluteUrl(relativeUrl: string): Promise; - getEntryPoint(): Promise; + getEntryPoint(): Promise; getLoadedCodeDetails(): IFluidCodeDetails | undefined; getQuorum(): IQuorumClients; getSpecifiedCodeDetails(): IFluidCodeDetails | undefined; @@ -446,7 +446,7 @@ export interface IResolvedFluidCodeDetails extends IFluidCodeDetails { // @public export interface IRuntime extends IDisposable { createSummary(blobRedirectTable?: Map): ISummaryTree; - getEntryPoint(): Promise; + getEntryPoint(): Promise; // @alpha getPendingLocalState(props?: IGetPendingLocalStateProps): unknown; notifyOpReplay?(message: ISequencedDocumentMessage): Promise; diff --git a/packages/common/container-definitions/src/loader.ts b/packages/common/container-definitions/src/loader.ts index 8521b49c3be4..b40d9b64b095 100644 --- a/packages/common/container-definitions/src/loader.ts +++ b/packages/common/container-definitions/src/loader.ts @@ -463,7 +463,7 @@ export interface IContainer extends IEventProvider { * 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; } /** diff --git a/packages/common/container-definitions/src/runtime.ts b/packages/common/container-definitions/src/runtime.ts index 480c735d3bbe..412b1c738aa3 100644 --- a/packages/common/container-definitions/src/runtime.ts +++ b/packages/common/container-definitions/src/runtime.ts @@ -104,7 +104,7 @@ export interface IRuntime extends IDisposable { * * @see {@link IContainer.getEntryPoint} */ - getEntryPoint(): Promise; + getEntryPoint(): Promise; } /** 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 9342076dec6f..1ffd6eedfe9b 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.api.md @@ -87,7 +87,7 @@ export type InitialObjects = { // @public (undocumented) export interface IProvideRootDataObject { // (undocumented) - readonly IRootDataObject?: IRootDataObject; + readonly IRootDataObject: IRootDataObject; } // @public diff --git a/packages/framework/fluid-static/package.json b/packages/framework/fluid-static/package.json index dd0e4df09fe4..0a5df2bea368 100644 --- a/packages/framework/fluid-static/package.json +++ b/packages/framework/fluid-static/package.json @@ -137,6 +137,9 @@ "RemovedClassDeclaration_ServiceAudience": { "forwardCompat": false, "backCompat": false + }, + "InterfaceDeclaration_IRootDataObject": { + "forwardCompat": false } } } 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 03eaeb6285c5..e5d71fc58103 100644 --- a/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts +++ b/packages/framework/fluid-static/src/test/types/validateFluidStaticPrevious.generated.ts @@ -199,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()); /* diff --git a/packages/framework/fluid-static/src/types.ts b/packages/framework/fluid-static/src/types.ts index bafc11535c7d..442cc8d1598b 100644 --- a/packages/framework/fluid-static/src/types.ts +++ b/packages/framework/fluid-static/src/types.ts @@ -97,7 +97,7 @@ export interface ContainerSchema { } export interface IProvideRootDataObject { - readonly IRootDataObject?: IRootDataObject; + readonly IRootDataObject: IRootDataObject; } /** diff --git a/packages/framework/tinylicious-client/src/TinyliciousClient.ts b/packages/framework/tinylicious-client/src/TinyliciousClient.ts index c5d705c644b3..4db49cdd021f 100644 --- a/packages/framework/tinylicious-client/src/TinyliciousClient.ts +++ b/packages/framework/tinylicious-client/src/TinyliciousClient.ts @@ -171,13 +171,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/loader/container-loader/src/container.ts b/packages/loader/container-loader/src/container.ts index aaf1bb5e44c3..38eef54e8cbe 100644 --- a/packages/loader/container-loader/src/container.ts +++ b/packages/loader/container-loader/src/container.ts @@ -705,14 +705,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, diff --git a/packages/service-clients/odsp-client/src/odspClient.ts b/packages/service-clients/odsp-client/src/odspClient.ts index 5c5259219247..b6ef7a61161e 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; } } From 6122b00f42dc2bb957e77259a9d0b0f5574918f2 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:24:49 -0800 Subject: [PATCH 40/50] Remove getRootDataStore (#18563) ## Breaking Changes ### 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. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/nice-worms-try.md | 11 +++++++++ .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../container-runtime-definitions.api.md | 3 --- .../src/containerRuntime.ts | 11 --------- .../api-report/container-runtime.api.md | 3 --- .../container-runtime/src/containerRuntime.ts | 24 ------------------- 6 files changed, 12 insertions(+), 42 deletions(-) create mode 100644 .changeset/nice-worms-try.md 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 6e3d1b61bd2c..93b1751aea2d 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -105,7 +105,7 @@ const entryPoint = await container.getEntryPoint(); | `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` 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 | | +| `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 | | | `IFluidHandleContext` on `IContainerRuntimeBase` | 2.0.0-internal.7.0.0 | | | `requestHandler` property in `ContainerRuntime.loadRuntime(...)` | 2.0.0-internal.7.0.0 | | 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 fae781d1d4d0..81f577743393 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,8 +39,6 @@ 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; diff --git a/packages/runtime/container-runtime-definitions/src/containerRuntime.ts b/packages/runtime/container-runtime-definitions/src/containerRuntime.ts index 1e8c8c252aac..9920a397701d 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, @@ -75,15 +73,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. 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 eca93168959a..5328901d5d0a 100644 --- a/packages/runtime/container-runtime/api-report/container-runtime.api.md +++ b/packages/runtime/container-runtime/api-report/container-runtime.api.md @@ -26,7 +26,6 @@ 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'; @@ -157,8 +156,6 @@ export class ContainerRuntime extends TypedEventEmitter; // (undocumented) getQuorum(): IQuorumClients; - // @deprecated - getRootDataStore(id: string, wait?: boolean): Promise; // (undocumented) idCompressor: (IIdCompressor & IIdCompressorCore) | undefined; // (undocumented) diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index c8d3aac318b2..b3859d6eba02 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, @@ -2388,28 +2386,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. From 9974920879b3e8b3a1b5f84e9fa5136fd9b4aa5d Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:41:34 -0800 Subject: [PATCH 41/50] Remove request pattern from IDataStore (#18572) ## Breaking Changes ### 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. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/tender-planets-beg.md | 9 ++++++ .../core-interfaces/Removing-IFluidRouter.md | 6 ++-- .../container-runtime/src/dataStore.ts | 16 +--------- .../api-report/runtime-definitions.api.md | 9 ------ .../runtime/runtime-definitions/package.json | 3 ++ .../src/dataStoreContext.ts | 32 ------------------- .../test/types/requestSignatureDeprecation.ts | 19 ----------- ...ateRuntimeDefinitionsPrevious.generated.ts | 1 + .../src/test/rootDatastores.spec.ts | 4 +-- 9 files changed, 19 insertions(+), 80 deletions(-) create mode 100644 .changeset/tender-planets-beg.md delete mode 100644 packages/runtime/runtime-definitions/src/test/types/requestSignatureDeprecation.ts 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 93b1751aea2d..11ef4d348e59 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -98,9 +98,9 @@ const entryPoint = await container.getEntryPoint(); | API | Deprecated in | Removed in | | -------------------------------------------------------------------------------------------- | -------------------- | -------------------- | | `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 | | +| `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 | | +| `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` and `FluidDataStoreRuntime` | 2.0.0-internal.6.0.0 | | @@ -111,7 +111,7 @@ const entryPoint = await container.getEntryPoint(); | `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 | 2.0.0-internal.8.0.0 | -| `request` and `IFluidRouter` on `IDataStore` | 2.0.0-internal.7.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 | | | `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 | 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/runtime-definitions/api-report/runtime-definitions.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md index ea5015427902..beedb847742d 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -151,15 +151,6 @@ export interface IContainerRuntimeBaseEvents extends IEvent { // @public 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; } diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index 00c872c461b6..c75366c1918e 100644 --- a/packages/runtime/runtime-definitions/package.json +++ b/packages/runtime/runtime-definitions/package.json @@ -95,6 +95,9 @@ }, "InterfaceDeclaration_IFluidDataStoreContextDetached": { "backCompat": false + }, + "InterfaceDeclaration_IDataStore": { + "backCompat": false } } } diff --git a/packages/runtime/runtime-definitions/src/dataStoreContext.ts b/packages/runtime/runtime-definitions/src/dataStoreContext.ts index 2d6cb9c72cec..9f946950c545 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -147,38 +147,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; } /** 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 7f64dd0ccff4..d8437e0dbc16 100644 --- a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts @@ -356,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()); /* 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 d94f22152f7e..8c626a0bc3b6 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 { describeFullCompat } from "@fluid-private/test-version-utils"; +import { IDataStore } from "@fluidframework/runtime-definitions"; describeFullCompat("Named root data stores", (getTestObjectProvider) => { let provider: ITestObjectProvider; @@ -217,7 +217,7 @@ describeFullCompat("Named root data stores", (getTestObjectProvider) => { "Trying to create multiple datastores aliased to the same value on the same client " + "will always return the same datastore", async () => { - const datastores: IFluidRouter[] = []; + const datastores: IDataStore[] = []; const createAliasedDataStore = async () => { try { await getAliasedDataStoreEntryPoint(dataObject1, alias); From 5eaca599f7477dab185538b915fcd1843a634bd9 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:41:59 -0800 Subject: [PATCH 42/50] Remove request pattern from RemoteFluidObjectHandle (#18573) [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .../src/remoteObjectHandle.ts | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) 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); - } - } } From 5608701886e53a684300dbd4ded06797430d48e6 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:10:07 -0800 Subject: [PATCH 43/50] Remove resolveHandle and IFluidHandleContext from ContainerRuntime interfaces (#18567) ## Breaking Changes ### 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. [AB#5593](https://dev.azure.com/fluidframework/internal/_workitems/edit/5593) --- .changeset/cute-points-obey.md | 10 +++++++ .../tree/src/test/utilities/TestUtilities.ts | 30 +++++++------------ .../core-interfaces/Removing-IFluidRouter.md | 4 +-- .../container-runtime-definitions.api.md | 2 -- .../src/containerRuntime.ts | 7 ----- .../api-report/runtime-definitions.api.md | 3 -- .../src/dataStoreContext.ts | 6 ---- .../src/test/connectionMode.spec.ts | 5 ---- .../src/test/documentDirty.spec.ts | 8 ----- .../src/test/opsOnReconnect.spec.ts | 7 ++--- .../src/test/SummarizeFetchValidation.spec.ts | 7 ++--- ...WithOutOfOrderDataStoreRealization.spec.ts | 7 ++--- .../src/test/SummarizerWithSearch.spec.ts | 7 ++--- .../src/test/audience.spec.ts | 5 ---- .../src/test/container.spec.ts | 13 ++------ .../src/test/createContainerStats.spec.ts | 5 ---- .../src/test/dataStoreRetrieval.spec.ts | 7 +---- .../src/test/fluidObjectHandle.spec.ts | 11 ++++--- .../test/gc/gcContainerRuntimeCompat.spec.ts | 5 ---- .../src/test/gc/gcTreeSummaryHandles.spec.ts | 6 +--- .../src/test/gc/gcVersionUpdate.spec.ts | 11 +------ .../test/incrementalSummaryContext.spec.ts | 5 ---- .../src/test/legacyChunking.spec.ts | 6 +--- .../src/test/localLoader.spec.ts | 10 ++----- .../src/test/summaryDataStoreStats.spec.ts | 6 +--- .../src/loadTestDataStore.ts | 6 +--- .../test/test-utils/src/TestSummaryUtils.ts | 4 --- .../test/test-utils/src/localCodeLoader.ts | 7 ----- .../test-version-utils/src/compatUtils.ts | 6 +--- 29 files changed, 49 insertions(+), 167 deletions(-) create mode 100644 .changeset/cute-points-obey.md 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/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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index 11ef4d348e59..d8e557f90cd1 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -106,8 +106,8 @@ const entryPoint = await container.getEntryPoint(); | `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 | 2.0.0-internal.8.0.0 | -| `resolveHandle` on `IContainerRuntime` | 2.0.0-internal.7.0.0 | | -| `IFluidHandleContext` on `IContainerRuntimeBase` | 2.0.0-internal.7.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 | 2.0.0-internal.8.0.0 | 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 81f577743393..244b3bf14ec2 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 @@ -42,8 +42,6 @@ export interface IContainerRuntime extends IProvideFluidDataStoreRegistry, ICont 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/src/containerRuntime.ts b/packages/runtime/container-runtime-definitions/src/containerRuntime.ts index 9920a397701d..942115f020d9 100644 --- a/packages/runtime/container-runtime-definitions/src/containerRuntime.ts +++ b/packages/runtime/container-runtime-definitions/src/containerRuntime.ts @@ -104,11 +104,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/runtime-definitions/api-report/runtime-definitions.api.md b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md index beedb847742d..c2f7d984456e 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -15,7 +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'; @@ -126,8 +125,6 @@ export interface IContainerRuntimeBase extends IEventProvider; getAudience(): IAudience; getQuorum(): IQuorumClients; - // @deprecated (undocumented) - readonly IFluidHandleContext: IFluidHandleContext; // (undocumented) readonly logger: ITelemetryBaseLogger; orderSequentially(callback: () => void): void; diff --git a/packages/runtime/runtime-definitions/src/dataStoreContext.ts b/packages/runtime/runtime-definitions/src/dataStoreContext.ts index 9f946950c545..bc044ef8fc18 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -15,7 +15,6 @@ import { IRequest, IResponse, FluidObject, - IFluidHandleContext, } from "@fluidframework/core-interfaces"; import { IAudience, @@ -214,11 +213,6 @@ export interface IContainerRuntimeBase extends IEventProvider { 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/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/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts b/packages/test/test-end-to-end-tests/src/test/SummarizeFetchValidation.spec.ts index cc2d617d429a..f46076b9bd78 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 { describeNoCompat, 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, }, ); 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 41e14fe56984..e63fbaa47a91 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 { describeNoCompat, 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 b11d7490c8e1..2e6a8a494342 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 @@ describeNoCompat("Prepare for Summary with Search Blobs", (getTestObjectProvider 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 @@ describeNoCompat("Prepare for Summary with Search Blobs", (getTestObjectProvider 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/audience.spec.ts b/packages/test/test-end-to-end-tests/src/test/audience.spec.ts index 81dbb06cba93..0ec3167b8f89 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 { describeFullCompat } from "@fluid-private/test-version-utils"; -import { IRequest } from "@fluidframework/core-interfaces"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { IContainer } from "@fluidframework/container-definitions"; describeFullCompat("Audience correctness", (getTestObjectProvider, apis) => { @@ -30,14 +28,11 @@ describeFullCompat("Audience correctness", (getTestObjectProvider, apis) => { [], [], ); - 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/container.spec.ts b/packages/test/test-end-to-end-tests/src/test/container.spec.ts index 79d81f2ab1ff..6159cfbb84d3 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 @@ -44,7 +44,6 @@ import { describeNoCompat, itExpects, } from "@fluid-private/test-version-utils"; -import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions"; import { ConfigTypes, DataCorruptionError, @@ -293,12 +292,8 @@ describeNoCompat("Container", (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, @@ -383,12 +378,8 @@ describeNoCompat("Container", (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 e949b7f0af44..d0ae978c8e30 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 @@ describeNoCompat("Generate Summary Stats", (getTestObjectProvider, apis) => { 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 084bb619ba62..501fce7fe9ac 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 { describeFullCompat, 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. @@ -86,8 +85,6 @@ describeFullCompat( ); let provider: ITestObjectProvider; - const innerRequestHandler = async (request: IRequest, runtime: IContainerRuntimeBase) => - runtime.IFluidHandleContext.resolveHandle(request); beforeEach(() => { provider = getTestObjectProvider(); @@ -102,7 +99,6 @@ describeFullCompat( [outerDataObjectFactory.type, Promise.resolve(outerDataObjectFactory)], [innerDataObjectFactory.type, Promise.resolve(innerDataObjectFactory)], ], - requestHandlers: [innerRequestHandler], }, ); const request = provider.driver.createCreateNewRequest(provider.documentId); @@ -128,7 +124,6 @@ describeFullCompat( [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/fluidObjectHandle.spec.ts b/packages/test/test-end-to-end-tests/src/test/fluidObjectHandle.spec.ts index 18267e8ac0ee..908c95b95b0e 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 @@ -16,6 +16,7 @@ import { ITestDataObject, TestDataObjectType, } from "@fluid-private/test-version-utils"; +import { ContainerRuntime } from "@fluidframework/container-runtime"; describeFullCompat("FluidObjectHandle", (getTestObjectProvider) => { let provider: ITestObjectProvider; @@ -49,8 +50,9 @@ describeFullCompat("FluidObjectHandle", (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, @@ -58,8 +60,9 @@ describeFullCompat("FluidObjectHandle", (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 d7104ee7e4ad..e6ab87828a7b 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 @@ describeFullCompat.skip("GC summary compatibility tests", (getTestObjectProvider }, 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 b3685a16c89b..399cf3e5008d 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, @@ -188,14 +187,11 @@ describeNoCompat("GC Tree stored as a handle in summaries", (getTestObjectProvid 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 18dc8729127c..74a6b106c302 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 { describeNoCompat } from "@fluid-private/test-version-utils"; -import { IRequest } from "@fluidframework/core-interfaces"; import { IGCMetadata, IGarbageCollector, @@ -55,15 +50,11 @@ describeNoCompat("GC version update", (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/incrementalSummaryContext.spec.ts b/packages/test/test-end-to-end-tests/src/test/incrementalSummaryContext.spec.ts index c3b0cdcfd045..7b74fade8a7f 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, @@ -471,12 +469,9 @@ describeNoCompat("Incremental summaries can be generated for DDSes", (getTestObj 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/localLoader.spec.ts b/packages/test/test-end-to-end-tests/src/test/localLoader.spec.ts index 320cb26a4d48..388650755757 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 @@ describeNoCompat("LocalLoader", (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 @@ describeNoCompat("LocalLoader", (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/summaryDataStoreStats.spec.ts b/packages/test/test-end-to-end-tests/src/test/summaryDataStoreStats.spec.ts index e167408459dc..7f3a0027b5a9 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 { describeNoCompat } from "@fluid-private/test-version-utils"; @@ -58,12 +57,9 @@ describeNoCompat("Generate Summary Stats", (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-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-utils/src/TestSummaryUtils.ts b/packages/test/test-utils/src/TestSummaryUtils.ts index 6edf4271e4c0..f6df1774e37e 100644 --- a/packages/test/test-utils/src/TestSummaryUtils.ts +++ b/packages/test/test-utils/src/TestSummaryUtils.ts @@ -14,7 +14,6 @@ import { import { ITelemetryBaseLogger, FluidObject, IRequest } from "@fluidframework/core-interfaces"; import { DriverHeader } from "@fluidframework/driver-definitions"; import { - IContainerRuntimeBase, IFluidDataStoreFactory, NamedFluidDataStoreRegistryEntries, } from "@fluidframework/runtime-definitions"; @@ -88,8 +87,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, { @@ -97,7 +94,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 2c3dadb4a9ba..f16367d25b64 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"; @@ -64,10 +62,6 @@ export class LocalCodeLoader implements ICodeDetailsLoader { "default", maybeExport.IFluidDataStoreFactory, ); - const innerRequestHandler = async ( - request: IRequest, - runtime: IContainerRuntimeBase, - ) => runtime.IFluidHandleContext.resolveHandle(request); fluidModule = { fluidExport: { ...maybeExport, @@ -76,7 +70,6 @@ export class LocalCodeLoader implements ICodeDetailsLoader { registryEntries: [ [defaultFactory.type, Promise.resolve(defaultFactory)], ], - requestHandlers: [innerRequestHandler], runtimeOptions, }), }, diff --git a/packages/test/test-version-utils/src/compatUtils.ts b/packages/test/test-version-utils/src/compatUtils.ts index 48bf92891560..6c3777aa911d 100644 --- a/packages/test/test-version-utils/src/compatUtils.ts +++ b/packages/test/test-version-utils/src/compatUtils.ts @@ -4,9 +4,8 @@ */ import { FluidTestDriverConfig, createFluidTestDriver } from "@fluid-private/test-drivers"; -import { IFluidLoadable, IRequest } from "@fluidframework/core-interfaces"; +import { IFluidLoadable } from "@fluidframework/core-interfaces"; import { - IContainerRuntimeBase, IFluidDataStoreContext, IFluidDataStoreFactory, } from "@fluidframework/runtime-definitions"; @@ -120,8 +119,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) => { @@ -135,7 +132,6 @@ export async function getVersionedTestObjectProviderFromApis( TestDataObjectType, dataStoreFactory, containerOptions?.runtimeOptions, - [innerRequestHandler], ); }; From 86d49ad0e98270e5cd4b57bf657fc4c30365e0d4 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:18:57 -0800 Subject: [PATCH 44/50] Remove IFluidRouter from DataObject classes (#18588) ## Breaking Changes ### 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. ### Removed `IRootDataObjectFactory` The `IRootDataObjectFactory` interface has been removed. Please remove all usage of it. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/gentle-hats-appear.md | 17 +++++++++++++++++ .changeset/nine-coins-send.md | 7 +++++++ .../aqueduct/api-report/aqueduct.api.md | 13 ++----------- packages/framework/aqueduct/package.json | 10 ++++++++++ .../src/data-object-factories/index.ts | 2 +- .../pureDataObjectFactory.ts | 16 ++-------------- .../src/data-objects/pureDataObject.ts | 11 +---------- packages/framework/aqueduct/src/index.ts | 6 +----- .../validateAqueductPrevious.generated.ts | 18 ++++-------------- .../api-report/data-object-base.api.md | 5 +---- .../framework/data-object-base/package.json | 6 +++++- .../src/lazyLoadedDataObject.ts | 15 +-------------- ...validateDataObjectBasePrevious.generated.ts | 1 + .../test-utils/api-report/test-utils.api.md | 4 +--- packages/test/test-utils/package.json | 6 +++++- .../validateTestUtilsPrevious.generated.ts | 1 + .../test/test-utils/src/testFluidObject.ts | 10 ---------- 17 files changed, 60 insertions(+), 88 deletions(-) create mode 100644 .changeset/gentle-hats-appear.md create mode 100644 .changeset/nine-coins-send.md 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/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/packages/framework/aqueduct/api-report/aqueduct.api.md b/packages/framework/aqueduct/api-report/aqueduct.api.md index f43c1e2e76da..be8482d31561 100644 --- a/packages/framework/aqueduct/api-report/aqueduct.api.md +++ b/packages/framework/aqueduct/api-report/aqueduct.api.md @@ -23,7 +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 { IFluidRouter } from '@fluidframework/core-interfaces'; import { IProvideFluidDataStoreRegistry } from '@fluidframework/runtime-definitions'; import { IProvideFluidHandle } from '@fluidframework/core-interfaces'; import { IRequest } from '@fluidframework/core-interfaces'; @@ -105,13 +104,7 @@ export interface IDataObjectProps { } // @public -export interface IRootDataObjectFactory extends IFluidDataStoreFactory { - // (undocumented) - createRootInstance(rootDataStoreId: string, runtime: IContainerRuntime): Promise; -} - -// @public -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; @@ -125,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; @@ -141,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 2855c9101b77..d6953a4d177e 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -156,6 +156,16 @@ "RemovedVariableDeclaration_mountableViewRequestHandler": { "forwardCompat": false, "backCompat": false + }, + "ClassDeclaration_DataObject": { + "backCompat": false + }, + "RemovedInterfaceDeclaration_IRootDataObjectFactory": { + "forwardCompat": false, + "backCompat": false + }, + "ClassDeclaration_PureDataObject": { + "backCompat": false } } } 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 7823b7efa4b0..213ff553ff86 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,14 +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 - * @public - */ -export interface IRootDataObjectFactory extends IFluidDataStoreFactory { - // eslint-disable-next-line import/no-deprecated - createRootInstance(rootDataStoreId: string, runtime: IContainerRuntime): Promise; -} /** * Proxy over PureDataObject @@ -135,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 ccfc06dc1cc0..cc252b07bd07 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, @@ -32,7 +30,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 +58,6 @@ export abstract class PureDataObject): void; use_old_ClassDeclaration_DataObject( + // @ts-expect-error compatibility expected to be broken get_current_ClassDeclaration_DataObject()); /* @@ -168,26 +169,14 @@ use_old_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 +200,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()); /* 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 8eba08f04ddc..5b3b42b69866 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'; // @public (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 a50cdb504338..db991f692185 100644 --- a/packages/framework/data-object-base/package.json +++ b/packages/framework/data-object-base/package.json @@ -96,6 +96,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 3aeb284f3279..bcf932e90112 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"; @@ -29,17 +27,10 @@ export abstract class LazyLoadedDataObject< > 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 +52,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/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/test/test-utils/api-report/test-utils.api.md b/packages/test/test-utils/api-report/test-utils.api.md index 7cf54957d8d6..fa2b4a86662d 100644 --- a/packages/test/test-utils/api-report/test-utils.api.md +++ b/packages/test/test-utils/api-report/test-utils.api.md @@ -281,13 +281,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 1d07c0810ee5..88d743ddd295 100644 --- a/packages/test/test-utils/package.json +++ b/packages/test/test-utils/package.json @@ -124,6 +124,10 @@ } }, "typeValidation": { - "broken": {} + "broken": { + "ClassDeclaration_TestFluidObject": { + "backCompat": false + } + } } } 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 bd7b24c49bed..f100f5541dc4 100644 --- a/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts +++ b/packages/test/test-utils/src/test/types/validateTestUtilsPrevious.generated.ts @@ -331,6 +331,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/testFluidObject.ts b/packages/test/test-utils/src/testFluidObject.ts index 91087bd6b3ca..ec7465425d5e 100644 --- a/packages/test/test-utils/src/testFluidObject.ts +++ b/packages/test/test-utils/src/testFluidObject.ts @@ -34,13 +34,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; } @@ -84,9 +77,6 @@ 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 { return request.url === "" || request.url === "/" || request.url.startsWith("/?") ? { mimeType: "fluid/object", status: 200, value: this } From 1b70ef40438ebb31146c7c0164fab7c25a37d066 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 14:48:14 -0800 Subject: [PATCH 45/50] Remove request pattern from FluidDataStore interfaces and classes (#18581) ## Breaking Changes ### 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. ### 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. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/dark-dancers-hug.md | 9 +++++++++ .changeset/shaggy-doodles-fetch.md | 10 ++++++++++ .../core-interfaces/Removing-IFluidRouter.md | 4 ++-- packages/framework/aqueduct/package.json | 3 +++ .../aqueduct/src/data-objects/pureDataObject.ts | 1 - .../types/validateAqueductPrevious.generated.ts | 1 + .../data-object-base/src/lazyLoadedDataObject.ts | 1 - .../api-report/datastore-definitions.api.md | 7 ------- .../runtime/datastore-definitions/package.json | 6 +++++- .../datastore-definitions/src/dataStoreRuntime.ts | 15 --------------- ...idateDatastoreDefinitionsPrevious.generated.ts | 1 + .../runtime/datastore/api-report/datastore.api.md | 2 -- packages/runtime/datastore/package.json | 3 ++- .../runtime/datastore/src/dataStoreRuntime.ts | 7 ------- .../types/validateDatastorePrevious.generated.ts | 1 + .../api-report/runtime-definitions.api.md | 3 --- packages/runtime/runtime-definitions/package.json | 3 +++ .../runtime-definitions/src/dataStoreContext.ts | 8 -------- ...alidateRuntimeDefinitionsPrevious.generated.ts | 1 + .../api-report/test-runtime-utils.api.md | 4 +--- packages/runtime/test-runtime-utils/src/mocks.ts | 10 ---------- 21 files changed, 39 insertions(+), 61 deletions(-) create mode 100644 .changeset/dark-dancers-hug.md create mode 100644 .changeset/shaggy-doodles-fetch.md 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/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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index d8e557f90cd1..d3e144ef135a 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -103,8 +103,8 @@ const entryPoint = await container.getEntryPoint(); | `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` and `FluidDataStoreRuntime` | 2.0.0-internal.6.0.0 | | -| `request` and `IFluidRouter` on `IFluidDataStoreChannel` | 2.0.0-internal.6.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 | diff --git a/packages/framework/aqueduct/package.json b/packages/framework/aqueduct/package.json index d6953a4d177e..84b0329af231 100644 --- a/packages/framework/aqueduct/package.json +++ b/packages/framework/aqueduct/package.json @@ -166,6 +166,9 @@ }, "ClassDeclaration_PureDataObject": { "backCompat": false + }, + "InterfaceDeclaration_IDataObjectProps": { + "backCompat": false } } } diff --git a/packages/framework/aqueduct/src/data-objects/pureDataObject.ts b/packages/framework/aqueduct/src/data-objects/pureDataObject.ts index cc252b07bd07..57a64a1ad788 100644 --- a/packages/framework/aqueduct/src/data-objects/pureDataObject.ts +++ b/packages/framework/aqueduct/src/data-objects/pureDataObject.ts @@ -29,7 +29,6 @@ import { DataObjectTypes, IDataObjectProps } from "./types"; */ export abstract class PureDataObject extends TypedEventEmitter - // eslint-disable-next-line import/no-deprecated implements IFluidLoadable, IProvideFluidHandle { /** diff --git a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts index 47b2a3d2c574..fcf361b4ca12 100644 --- a/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts +++ b/packages/framework/aqueduct/src/test/types/validateAqueductPrevious.generated.ts @@ -164,6 +164,7 @@ 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()); /* diff --git a/packages/framework/data-object-base/src/lazyLoadedDataObject.ts b/packages/framework/data-object-base/src/lazyLoadedDataObject.ts index bcf932e90112..25eb2e8dbafc 100644 --- a/packages/framework/data-object-base/src/lazyLoadedDataObject.ts +++ b/packages/framework/data-object-base/src/lazyLoadedDataObject.ts @@ -26,7 +26,6 @@ export abstract class LazyLoadedDataObject< TEvents extends IEvent = IEvent, > extends EventForwarder - // eslint-disable-next-line import/no-deprecated implements IFluidLoadable, IProvideFluidHandle { private _handle?: IFluidHandle; 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 252a2b86d56c..f71092a556c0 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; diff --git a/packages/runtime/datastore-definitions/package.json b/packages/runtime/datastore-definitions/package.json index d17d543513e2..35e80050815f 100644 --- a/packages/runtime/datastore-definitions/package.json +++ b/packages/runtime/datastore-definitions/package.json @@ -66,6 +66,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 c4856ae2e63d..019faf790167 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, @@ -136,15 +132,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/test/types/validateDatastoreDefinitionsPrevious.generated.ts b/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts index f9441dfdb0cc..db1dfface195 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()); /* diff --git a/packages/runtime/datastore/api-report/datastore.api.md b/packages/runtime/datastore/api-report/datastore.api.md index 6a4ccc9521a4..29bd89dc9c03 100644 --- a/packages/runtime/datastore/api-report/datastore.api.md +++ b/packages/runtime/datastore/api-report/datastore.api.md @@ -88,8 +88,6 @@ export class FluidDataStoreRuntime extends TypedEventEmitter; - /** - * @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/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 c2f7d984456e..fb18494b91eb 100644 --- a/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md +++ b/packages/runtime/runtime-definitions/api-report/runtime-definitions.api.md @@ -15,7 +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 { IFluidRouter } from '@fluidframework/core-interfaces'; import { ILoaderOptions } from '@fluidframework/container-definitions'; import { IProvideFluidHandleContext } from '@fluidframework/core-interfaces'; import { IQuorumClients } from '@fluidframework/protocol-definitions'; @@ -188,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; diff --git a/packages/runtime/runtime-definitions/package.json b/packages/runtime/runtime-definitions/package.json index c75366c1918e..7df0c130a07e 100644 --- a/packages/runtime/runtime-definitions/package.json +++ b/packages/runtime/runtime-definitions/package.json @@ -98,6 +98,9 @@ }, "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 bc044ef8fc18..ea977b144b72 100644 --- a/packages/runtime/runtime-definitions/src/dataStoreContext.ts +++ b/packages/runtime/runtime-definitions/src/dataStoreContext.ts @@ -8,8 +8,6 @@ import { IEventProvider, ITelemetryBaseLogger, IDisposable, - // eslint-disable-next-line import/no-deprecated - IFluidRouter, IProvideFluidHandleContext, IFluidHandle, IRequest, @@ -317,12 +315,6 @@ export interface IFluidDataStoreChannel extends IDisposable { readonly entryPoint: IFluidHandle; 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; } export type CreateChildSummarizerNodeFn = ( 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 d8437e0dbc16..551e8692477f 100644 --- a/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts +++ b/packages/runtime/runtime-definitions/src/test/types/validateRuntimeDefinitionsPrevious.generated.ts @@ -429,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()); /* 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 ba9ed87e312c..434f210fa76f 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 @@ -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/src/mocks.ts b/packages/runtime/test-runtime-utils/src/mocks.ts index e639f066b891..8e286eeaa8d2 100644 --- a/packages/runtime/test-runtime-utils/src/mocks.ts +++ b/packages/runtime/test-runtime-utils/src/mocks.ts @@ -605,13 +605,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; @@ -772,9 +765,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; } From aaa08a21bc029a099702bd1382b92a04f96da043 Mon Sep 17 00:00:00 2001 From: Kian Thompson <102998837+kian-thompson@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:01:55 -0800 Subject: [PATCH 46/50] Remove IFluidRouter (#18599) ## Breaking Changes ### 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. [AB#4991](https://dev.azure.com/fluidframework/internal/_workitems/edit/4991) --- .changeset/sweet-pandas-chew.md | 9 ++ .../core-interfaces/Removing-IFluidRouter.md | 2 +- .../api-report/core-interfaces.api.md | 15 --- packages/common/core-interfaces/package.json | 15 ++- .../common/core-interfaces/src/fluidRouter.ts | 20 --- packages/common/core-interfaces/src/index.ts | 8 +- ...alidateCoreInterfacesPrevious.generated.ts | 48 +------ .../src/test/dependencyContainer.spec.ts | 125 +++++++++--------- .../src/test/container.spec.ts | 2 - 9 files changed, 93 insertions(+), 151 deletions(-) create mode 100644 .changeset/sweet-pandas-chew.md 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/packages/common/core-interfaces/Removing-IFluidRouter.md b/packages/common/core-interfaces/Removing-IFluidRouter.md index d3e144ef135a..7ddf49d6a8d6 100644 --- a/packages/common/core-interfaces/Removing-IFluidRouter.md +++ b/packages/common/core-interfaces/Removing-IFluidRouter.md @@ -112,7 +112,7 @@ const entryPoint = await container.getEntryPoint(); | `RuntimeRequestHandler` and `RuntimeRequestHandlerBuilder` | 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 | | +| `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 | 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 3458d73c2c82..16b769eab110 100644 --- a/packages/common/core-interfaces/api-report/core-interfaces.api.md +++ b/packages/common/core-interfaces/api-report/core-interfaces.api.md @@ -297,15 +297,6 @@ export interface IFluidPackageEnvironment { }; } -// @public @deprecated (undocumented) -export const IFluidRouter: keyof IProvideFluidRouter; - -// @public @deprecated (undocumented) -export interface IFluidRouter extends IProvideFluidRouter { - // (undocumented) - request(request: IRequest): Promise; -} - // @public (undocumented) export const IFluidRunnable: keyof IProvideFluidRunnable; @@ -353,12 +344,6 @@ export interface IProvideFluidLoadable { readonly IFluidLoadable: IFluidLoadable; } -// @public @deprecated -export interface IProvideFluidRouter { - // (undocumented) - readonly IFluidRouter: IFluidRouter; -} - // @public (undocumented) export interface IProvideFluidRunnable { // (undocumented) diff --git a/packages/common/core-interfaces/package.json b/packages/common/core-interfaces/package.json index 9e5ae38a5e45..076a4dab0157 100644 --- a/packages/common/core-interfaces/package.json +++ b/packages/common/core-interfaces/package.json @@ -67,6 +67,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 78239e072eac..409b036fd299 100644 --- a/packages/common/core-interfaces/src/fluidRouter.ts +++ b/packages/common/core-interfaces/src/fluidRouter.ts @@ -25,23 +25,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 - */ -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 - */ -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 - */ -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 3d857c576569..e93ff363b8b5 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/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/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; From 788bd9a0c3cf0cb04cfacd94368ad68ecdf2580d Mon Sep 17 00:00:00 2001 From: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:31:52 -0800 Subject: [PATCH 47/50] remove(location-redirection-utils): Remove package from the repository (#18202) The package has been deprecated for some time, and had very few external usages. The time has come to remove it from the repo. [AB#5070](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/5070) --- .changeset/copied-llamas-digest.md | 8 ++ PACKAGES.md | 3 +- README.md | 2 + feeds/internal-build.txt | 1 - feeds/internal-test.txt | 1 - feeds/public.txt | 1 - .../location-redirection-utils/.eslintrc.js | 16 --- .../location-redirection-utils/.gitignore | 2 - .../location-redirection-utils/.mocharc.js | 12 -- .../location-redirection-utils/.npmignore | 6 - .../location-redirection-utils/CHANGELOG.md | 79 ------------- .../loader/location-redirection-utils/LICENSE | 21 ---- .../location-redirection-utils/README.md | 84 -------------- .../api-extractor.json | 15 --- .../location-redirection-utils.api.md | 14 --- .../location-redirection-utils/package.json | 106 ------------------ .../prettier.config.cjs | 8 -- .../location-redirection-utils/src/index.ts | 23 ---- .../src/test/tsconfig.json | 16 --- ...ationRedirectionUtilsPrevious.generated.ts | 70 ------------ .../tsconfig.esnext.json | 7 -- .../location-redirection-utils/tsconfig.json | 10 -- pnpm-lock.yaml | 66 ----------- 23 files changed, 11 insertions(+), 560 deletions(-) create mode 100644 .changeset/copied-llamas-digest.md delete mode 100644 packages/loader/location-redirection-utils/.eslintrc.js delete mode 100644 packages/loader/location-redirection-utils/.gitignore delete mode 100644 packages/loader/location-redirection-utils/.mocharc.js delete mode 100644 packages/loader/location-redirection-utils/.npmignore delete mode 100644 packages/loader/location-redirection-utils/CHANGELOG.md delete mode 100644 packages/loader/location-redirection-utils/LICENSE delete mode 100644 packages/loader/location-redirection-utils/README.md delete mode 100644 packages/loader/location-redirection-utils/api-extractor.json delete mode 100644 packages/loader/location-redirection-utils/api-report/location-redirection-utils.api.md delete mode 100644 packages/loader/location-redirection-utils/package.json delete mode 100644 packages/loader/location-redirection-utils/prettier.config.cjs delete mode 100644 packages/loader/location-redirection-utils/src/index.ts delete mode 100644 packages/loader/location-redirection-utils/src/test/tsconfig.json delete mode 100644 packages/loader/location-redirection-utils/src/test/types/validateLocationRedirectionUtilsPrevious.generated.ts delete mode 100644 packages/loader/location-redirection-utils/tsconfig.esnext.json delete mode 100644 packages/loader/location-redirection-utils/tsconfig.json 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/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/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/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-extractor.json b/packages/loader/location-redirection-utils/api-extractor.json deleted file mode 100644 index d64fef4ce661..000000000000 --- a/packages/loader/location-redirection-utils/api-extractor.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$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 - }, - "messages": { - "extractorMessageReporting": { - "ae-missing-release-tag": { - // This package is scheduled for deletion, so no need to clean this up. - "logLevel": "none" - } - } - } -} 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 1b2307693197..000000000000 --- a/packages/loader/location-redirection-utils/package.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "name": "@fluidframework/location-redirection-utils", - "version": "2.0.0-internal.8.0.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: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": { - "@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": "^16.18.38", - "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/pnpm-lock.yaml b/pnpm-lock.yaml index 03cef3cffce1..9b1df3c062b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8951,61 +8951,6 @@ importers: sinon: 7.5.0 typescript: 5.1.6 - packages/loader/location-redirection-utils: - specifiers: - '@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': ^16.18.38 - 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: - '@fluid-tools/build-cli': 0.28.0_dgt3pekvdbgd2jliwm43ktglpe - '@fluidframework/build-common': 2.0.3 - '@fluidframework/build-tools': 0.28.0_@types+node@16.18.65 - '@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_p4gemz6xo2owxftfuvjusnappy_@types+node@16.18.65 - '@types/mocha': 9.1.1 - '@types/node': 16.18.65 - 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:~ @@ -16763,17 +16708,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: From 18eaefcb10fa4702a7416e06cdac50ea7c95a479 Mon Sep 17 00:00:00 2001 From: Tony Murphy Date: Thu, 7 Dec 2023 16:44:50 -0800 Subject: [PATCH 48/50] Decouple Fluid-Static and the Service Clients From IFluidDataStoreFactory (#18688) This change decouples DataObjectClass from the IFluidDataStoreFactory but still requires a type of the same shape. This will help reduce the amount of our surface area we need to make public when we make AzureClient public. This is a non-breaking change for existing usages, and still allows the usage of DataObject which are an undocumented feature. --------- Co-authored-by: Tony Murphy --- .../api-report/fluid-static.api.md | 5 ++-- .../fluid-static/src/rootDataObject.ts | 10 +++++--- packages/framework/fluid-static/src/types.ts | 3 +-- packages/framework/fluid-static/src/utils.ts | 23 ++++++++++++++++--- 4 files changed, 31 insertions(+), 10 deletions(-) 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 da6794f896c4..c9b4fb0a6879 100644 --- a/packages/framework/fluid-static/api-report/fluid-static.api.md +++ b/packages/framework/fluid-static/api-report/fluid-static.api.md @@ -12,7 +12,6 @@ import { IContainer } from '@fluidframework/container-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'; @@ -41,7 +40,9 @@ export function createServiceAudience(props: { // @internal export type DataObjectClass = { - readonly factory: IFluidDataStoreFactory; + readonly factory: { + IFluidDataStoreFactory: DataObjectClass["factory"]; + }; } & LoadableObjectCtor; // @internal diff --git a/packages/framework/fluid-static/src/rootDataObject.ts b/packages/framework/fluid-static/src/rootDataObject.ts index 2429f602aeb4..032043ef0362 100644 --- a/packages/framework/fluid-static/src/rootDataObject.ts +++ b/packages/framework/fluid-static/src/rootDataObject.ts @@ -15,14 +15,18 @@ 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]; diff --git a/packages/framework/fluid-static/src/types.ts b/packages/framework/fluid-static/src/types.ts index 1692a2f4a5c6..1b037900df37 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 = * @internal */ export type DataObjectClass = { - readonly factory: IFluidDataStoreFactory; + readonly factory: { IFluidDataStoreFactory: DataObjectClass["factory"] }; } & LoadableObjectCtor; /** 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 + ); }; /** From c6cda2ffebe839e161611f0e6f108c765d4070a0 Mon Sep 17 00:00:00 2001 From: Tony Murphy Date: Fri, 8 Dec 2023 16:01:03 -0800 Subject: [PATCH 49/50] Update Common Packages in Server (#18726) Update common dependencies in server to propagate tagging. --------- Co-authored-by: Tony Murphy --- common/lib/common-utils/package.json | 2 +- common/lib/common-utils/pnpm-lock.yaml | 8 +- .../packages/kafka-orderer/package.json | 2 +- .../packages/lambdas-driver/package.json | 2 +- .../packages/lambdas/package.json | 4 +- .../packages/local-server/package.json | 4 +- .../packages/memory-orderer/package.json | 4 +- .../packages/protocol-base/package.json | 4 +- .../packages/routerlicious-base/package.json | 4 +- .../packages/routerlicious/package.json | 4 +- .../packages/services-client/package.json | 4 +- .../packages/services-core/package.json | 4 +- .../services-ordering-rdkafka/package.json | 2 +- .../packages/services-shared/package.json | 4 +- .../packages/services-telemetry/package.json | 2 +- .../packages/services-utils/package.json | 2 +- .../packages/services/package.json | 4 +- .../packages/test-utils/package.json | 4 +- .../packages/tinylicious/package.json | 4 +- server/routerlicious/pnpm-lock.yaml | 134 ++++++++++-------- 20 files changed, 110 insertions(+), 92 deletions(-) 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/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/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-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 6ba93bfaf2fa..8bbb12b34760 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==} From 2c8e405c127c777770fcb4608e742a7ac533ac69 Mon Sep 17 00:00:00 2001 From: Jason Hartman Date: Mon, 11 Dec 2023 12:30:16 -0800 Subject: [PATCH 50/50] improvement(client)!: enhance Jsonable types (#18523) ## Breaking Changes ### Alpha Require first generic parameter for `Jsonable` and `Serializable` types that was previously `any` and resulted in `any`. Additionally alter types to use new `JsonableTypeWith<>` as result when `any` or `unknown` is specified such that some constraint is actually produced. ### Internal devtools-core types were broadly broken as its `Primitive` type was updated to respect primitives supported by `Jsonable`. ## Notes This is a type only product change. Some tests were altered beyond type changes to respect the new constraints. Some deprecated and experimental uses of [unqualified] `Jsonable` or `Serializable` have been replaced by `any`. Other uses have been updated to specify a generic parameter, which may be `unknown` and now use `JsonableTypeWith` typing. Due to recursive `TypeOnly` filter used in type tests, the definition for `JsonableTypeWith` uses interfaces where there is nested typing, which limits recursion. This may cause complications like imprecise typing in the future and need to be switch to pure type alias recursion. Which means the type test generation will need to be updated to handle such types. --- .changeset/easy-deer-retire.md | 20 +++ .../data-object-grid/src/dataObjectGrid.ts | 2 +- .../bubblebench/common/src/types.ts | 2 +- .../benchmarks/bubblebench/ot/src/main.ts | 5 +- .../sharedtree/src/proxy/treeutils.ts | 9 +- .../bubblebench/sharedtree/src/state.ts | 3 +- examples/data-objects/table-view/package.json | 1 + examples/data-objects/table-view/src/grid.ts | 8 +- .../json1/api-report/sharejs-json1.api.md | 4 +- .../dds/ot/sharejs/json1/src/json1.ts | 4 +- .../ot/sharejs/json1/src/test/json1.spec.ts | 15 +- .../api-report/sequence-deprecated.api.md | 2 +- .../sequence-deprecated/src/sparsematrix.ts | 3 +- .../tree/api-report/experimental-tree.api.md | 3 +- .../dds/tree/src/persisted-types/0.0.2.ts | 3 +- .../tree2/src/test/domains/json/benchmarks.ts | 3 +- .../data-objects/src/signaler/signaler.ts | 22 +-- .../dds/map/src/test/mocha/map.fuzz.spec.ts | 2 +- packages/dds/matrix/src/ops.ts | 2 +- packages/dds/matrix/src/serialization.ts | 4 +- packages/dds/matrix/src/test/matrix.spec.ts | 2 +- .../dds/matrix/src/test/matrix.undo.spec.ts | 2 +- .../dds/sequence/api-report/sequence.api.md | 2 +- packages/dds/sequence/src/sharedSequence.ts | 12 +- .../shared-summary-block/src/interfaces.ts | 3 + .../src/sharedSummaryBlock.ts | 4 +- packages/framework/attributor/src/encoders.ts | 5 +- .../sharedString.attribution.spec.ts | 114 ++++++++++++- .../api-report/datastore-definitions.api.md | 15 +- .../datastore-definitions/src/index.ts | 2 +- .../datastore-definitions/src/jsonable.ts | 84 ++++++++-- .../datastore-definitions/src/serializable.ts | 2 +- .../src/test/types/jsonable.ts | 151 +++++++++++++++++- ...eDatastoreDefinitionsPrevious.generated.ts | 8 +- .../src/assertionShortCodesMap.ts | 2 +- .../src/test/SummarizeFetchValidation.spec.ts | 2 +- .../src/test/benchmark/PasTable.all.spec.ts | 12 +- .../src/test/cellEndToEndTests.spec.ts | 2 +- .../src/test/orderSequentially.spec.ts | 45 ++---- .../api-report/devtools-core.api.md | 2 +- .../tools/devtools/devtools-core/package.json | 36 +++++ .../src/data-visualization/VisualTree.ts | 4 +- .../validateDevtoolsCorePrevious.generated.ts | 12 ++ .../data-visualization/CommonInterfaces.ts | 2 +- pnpm-lock.yaml | 2 + 45 files changed, 517 insertions(+), 127 deletions(-) create mode 100644 .changeset/easy-deer-retire.md 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/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/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/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/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/data-objects/table-view/package.json b/examples/data-objects/table-view/package.json index edd1c55d6355..6e74973f5db0 100644 --- a/examples/data-objects/table-view/package.json +++ b/examples/data-objects/table-view/package.json @@ -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/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/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/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/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/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/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/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/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/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/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/sequence/api-report/sequence.api.md b/packages/dds/sequence/api-report/sequence.api.md index 1bce55ab64fa..1e1fd86be6a5 100644 --- a/packages/dds/sequence/api-report/sequence.api.md +++ b/packages/dds/sequence/api-report/sequence.api.md @@ -711,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) diff --git a/packages/dds/sequence/src/sharedSequence.ts b/packages/dds/sequence/src/sharedSequence.ts index 8c9df3c11148..e9fb1ed445e9 100644 --- a/packages/dds/sequence/src/sharedSequence.ts +++ b/packages/dds/sequence/src/sharedSequence.ts @@ -31,7 +31,7 @@ export class SubSequence 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/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/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/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/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/runtime/datastore-definitions/api-report/datastore-definitions.api.md b/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md index 61bd993060b7..18244e13f80d 100644 --- a/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md +++ b/packages/runtime/datastore-definitions/api-report/datastore-definitions.api.md @@ -137,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/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 db1dfface195..1d393b2d6fcc 100644 --- a/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts +++ b/packages/runtime/datastore-definitions/src/test/types/validateDatastoreDefinitionsPrevious.generated.ts @@ -246,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()); @@ -256,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( @@ -270,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()); @@ -280,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/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/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 43ebec230b39..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 @@ -141,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/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/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/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 9e5b8344aa6a..212edf92ba40 100644 --- a/packages/tools/devtools/devtools-core/package.json +++ b/packages/tools/devtools/devtools-core/package.json @@ -133,6 +133,42 @@ }, "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/test/types/validateDevtoolsCorePrevious.generated.ts b/packages/tools/devtools/devtools-core/src/test/types/validateDevtoolsCorePrevious.generated.ts index 55dc38756df7..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 @@ -1545,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()); /* @@ -1617,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()); /* @@ -2697,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()); /* @@ -2817,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()); /* @@ -3081,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()); /* @@ -3105,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()); /* @@ -3129,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()); /* @@ -3153,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()); /* @@ -3177,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()); /* @@ -3201,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()); /* @@ -3249,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()); /* @@ -3273,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-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/pnpm-lock.yaml b/pnpm-lock.yaml index c5be35bada46..6c8a3728d9d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2946,6 +2946,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 @@ -2974,6 +2975,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