From eda9e6dc55744cdc6b23a12b392f8bf5bc34d168 Mon Sep 17 00:00:00 2001 From: "Craig Macomber (Microsoft)" <42876482+CraigMacomber@users.noreply.github.com> Date: Wed, 1 May 2024 17:17:40 -0700 Subject: [PATCH] Create tree-react-api package (#20573) ## Description Add tree-react-api package. This package provides some experimental utilities for working with SharedTree, react and DataObjects. --- .../data-objects/inventory-app/package.json | 1 + .../data-objects/inventory-app/src/index.ts | 6 +- .../inventory-app/src/inventoryList.ts | 20 +- .../inventory-app/src/view/inventoryList.tsx | 15 +- .../inventory-app/tests/main.test.ts | 28 --- .../framework/tree-react-api/.eslintrc.cjs | 11 ++ .../framework/tree-react-api/.mocharc.cjs | 13 ++ .../framework/tree-react-api/.npmignore | 6 + .../framework/tree-react-api/CHANGELOG.md | 1 + experimental/framework/tree-react-api/LICENSE | 21 ++ .../framework/tree-react-api/README.md | 91 +++++++++ .../tree-react-api/api-extractor-lint.json | 4 + .../tree-react-api/api-extractor.json | 4 + .../api-report/tree-react-api.api.md | 56 ++++++ .../framework/tree-react-api/package.json | 132 +++++++++++++ .../tree-react-api/prettier.config.cjs | 8 + .../framework/tree-react-api/src/index.ts | 18 ++ .../src/reactSharedTreeView.tsx | 180 +++++++++++------- .../src/test/reactSharedTreeView.spec.ts | 43 +++++ .../tree-react-api/src/test/tsconfig.cjs.json | 12 ++ .../tree-react-api/src/test/tsconfig.json | 14 ++ .../framework/tree-react-api/src/useTree.ts | 30 +++ .../tree-react-api/tsconfig.cjs.json | 7 + .../framework/tree-react-api/tsconfig.json | 9 + feeds/internal-build.txt | 1 + feeds/internal-test.txt | 1 + feeds/public.txt | 1 + packages/framework/fluid-static/src/types.ts | 9 +- pnpm-lock.yaml | 78 +++++++- 29 files changed, 683 insertions(+), 137 deletions(-) create mode 100644 experimental/framework/tree-react-api/.eslintrc.cjs create mode 100644 experimental/framework/tree-react-api/.mocharc.cjs create mode 100644 experimental/framework/tree-react-api/.npmignore create mode 100644 experimental/framework/tree-react-api/CHANGELOG.md create mode 100644 experimental/framework/tree-react-api/LICENSE create mode 100644 experimental/framework/tree-react-api/README.md create mode 100644 experimental/framework/tree-react-api/api-extractor-lint.json create mode 100644 experimental/framework/tree-react-api/api-extractor.json create mode 100644 experimental/framework/tree-react-api/api-report/tree-react-api.api.md create mode 100644 experimental/framework/tree-react-api/package.json create mode 100644 experimental/framework/tree-react-api/prettier.config.cjs create mode 100644 experimental/framework/tree-react-api/src/index.ts rename {examples/data-objects/inventory-app => experimental/framework/tree-react-api}/src/reactSharedTreeView.tsx (58%) create mode 100644 experimental/framework/tree-react-api/src/test/reactSharedTreeView.spec.ts create mode 100644 experimental/framework/tree-react-api/src/test/tsconfig.cjs.json create mode 100644 experimental/framework/tree-react-api/src/test/tsconfig.json create mode 100644 experimental/framework/tree-react-api/src/useTree.ts create mode 100644 experimental/framework/tree-react-api/tsconfig.cjs.json create mode 100644 experimental/framework/tree-react-api/tsconfig.json diff --git a/examples/data-objects/inventory-app/package.json b/examples/data-objects/inventory-app/package.json index f858c439b69a..c5b696070445 100644 --- a/examples/data-objects/inventory-app/package.json +++ b/examples/data-objects/inventory-app/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@fluid-example/example-utils": "workspace:~", + "@fluid-experimental/tree-react-api": "workspace:~", "@fluidframework/aqueduct": "workspace:~", "@fluidframework/core-interfaces": "workspace:~", "@fluidframework/datastore-definitions": "workspace:~", diff --git a/examples/data-objects/inventory-app/src/index.ts b/examples/data-objects/inventory-app/src/index.ts index bdc7a88b7db8..c57b7d0d5096 100644 --- a/examples/data-objects/inventory-app/src/index.ts +++ b/examples/data-objects/inventory-app/src/index.ts @@ -6,11 +6,13 @@ import { ContainerViewRuntimeFactory } from "@fluid-example/example-utils"; import React from "react"; -import { InventoryList, InventoryListFactory } from "./inventoryList.js"; +import type { IReactTreeDataObject } from "@fluid-experimental/tree-react-api"; +import { InventoryListFactory } from "./inventoryList.js"; import { MainView } from "./view/inventoryList.js"; +import type { Inventory } from "./schema.js"; export const fluidExport = new ContainerViewRuntimeFactory( InventoryListFactory, - (tree: InventoryList) => + (tree: IReactTreeDataObject) => React.createElement(tree.TreeViewComponent, { viewComponent: MainView }), ); diff --git a/examples/data-objects/inventory-app/src/inventoryList.ts b/examples/data-objects/inventory-app/src/inventoryList.ts index d30037c1b560..23d1dd953a12 100644 --- a/examples/data-objects/inventory-app/src/inventoryList.ts +++ b/examples/data-objects/inventory-app/src/inventoryList.ts @@ -3,22 +3,8 @@ * Licensed under the MIT License. */ -// Lint rule can be disabled once eslint config is upgraded to 5.3.0+ // eslint-disable-next-line import/no-internal-modules -import { DataObjectFactory } from "@fluidframework/aqueduct/internal"; +import { treeDataObjectInternal } from "@fluid-experimental/tree-react-api/internal"; +import { treeConfiguration } from "./schema.js"; -import { TreeDataObject, factory } from "./reactSharedTreeView.js"; -import { type Inventory, treeConfiguration } from "./schema.js"; - -// For use with lower level APIs, like ContainerViewRuntimeFactory from "@fluid-example/example-utils". -export class InventoryList extends TreeDataObject { - public readonly key = "tree"; - public readonly config = treeConfiguration; -} - -export const InventoryListFactory = new DataObjectFactory( - "@fluid-experimental/inventory-list", - InventoryList, - [factory], - {}, -); +export const InventoryListFactory = treeDataObjectInternal("tree", treeConfiguration).factory; diff --git a/examples/data-objects/inventory-app/src/view/inventoryList.tsx b/examples/data-objects/inventory-app/src/view/inventoryList.tsx index 46e08fc4a55f..bd8f4c1ec12b 100644 --- a/examples/data-objects/inventory-app/src/view/inventoryList.tsx +++ b/examples/data-objects/inventory-app/src/view/inventoryList.tsx @@ -3,25 +3,14 @@ * Licensed under the MIT License. */ -import { Tree } from "@fluidframework/tree"; +import { useTree } from "@fluid-experimental/tree-react-api"; import * as React from "react"; import { Inventory } from "../schema.js"; - import { Counter } from "./counter.js"; export const MainView: React.FC<{ root: Inventory }> = ({ root: inventory }) => { - // Use a React effect hook to invalidate this component when the inventory changes. - // We do this by incrementing a counter, which is passed as a dependency to the effect hook. - const [invalidations, setInvalidations] = React.useState(0); - - // React effect hook that increments the 'invalidation' counter whenever inventory or any of its children change. - React.useEffect(() => { - // Returns the cleanup function to be invoked when the component unmounts. - return Tree.on(inventory, "treeChanged", () => { - setInvalidations((i) => i + 1); - }); - }, [invalidations, inventory]); + useTree(inventory); const counters: JSX.Element[] = []; diff --git a/examples/data-objects/inventory-app/tests/main.test.ts b/examples/data-objects/inventory-app/tests/main.test.ts index 3eb494b64e88..ca94b8cac656 100644 --- a/examples/data-objects/inventory-app/tests/main.test.ts +++ b/examples/data-objects/inventory-app/tests/main.test.ts @@ -22,31 +22,3 @@ describe.skip("Integration Tests", () => { // await expect(page).toClick("button", { text: "Roll" }); }); }); - -describe("Unit tests", () => { - // Demonstrates use with high level service-client container API's like AzureClient and OdspClient. - it("treeDataObject works with container schema", () => { - // TODO: There is no service implementation agnostic client abstraction that can be referred to here (ex: shared by AzureClient and OdspClient). - // This makes documenting compatibility with that implicit common API difficult. - // It also makes writing service agnostic code at that abstraction level harder. - // This should be fixed. - // - // TODO: - // Writing an app at this abstraction level currently requires a lot of boilerplate which also requires extra dependencies. - // Since `@fluid-example/example-utils` doesn't provide that boilerplate and neither do the public packages, there isn't a concise way to actually use this container in this example. - // This should be fixed. - // - // TODO: - // The commonly used boilerplate for setting up a ContainerSchema based application configures the dev-tools, which would be great to include in this example, - // but can't be included due to dependency layering issues. - // - // TODO: THis test setup fails to import files from src, and also errors on unused values, so this can't be enabled. - // const containerSchema = { - // initialObjects: { - // // TODO: it seems odd that DataObjects in container schema need both a key under initialObjects where they are, - // // as well as a key under the root data object, and SharedObjects only need one key. - // tree: treeDataObject("tree", treeConfiguration), - // }, - // } satisfies ContainerSchema; - }); -}); diff --git a/experimental/framework/tree-react-api/.eslintrc.cjs b/experimental/framework/tree-react-api/.eslintrc.cjs new file mode 100644 index 000000000000..f1aaefc67a58 --- /dev/null +++ b/experimental/framework/tree-react-api/.eslintrc.cjs @@ -0,0 +1,11 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +module.exports = { + extends: [require.resolve("@fluidframework/eslint-config-fluid/strict"), "prettier"], + parserOptions: { + project: ["./tsconfig.json", "./src/test/tsconfig.json"], + }, +}; diff --git a/experimental/framework/tree-react-api/.mocharc.cjs b/experimental/framework/tree-react-api/.mocharc.cjs new file mode 100644 index 000000000000..1c260b69c61c --- /dev/null +++ b/experimental/framework/tree-react-api/.mocharc.cjs @@ -0,0 +1,13 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +"use strict"; + +const getFluidTestMochaConfig = require("@fluid-internal/mocha-test-setup/mocharc-common"); + +const packageDir = __dirname; +const config = getFluidTestMochaConfig(packageDir); +config.spec = process.env.MOCHA_SPEC ?? "lib/test"; +module.exports = config; diff --git a/experimental/framework/tree-react-api/.npmignore b/experimental/framework/tree-react-api/.npmignore new file mode 100644 index 000000000000..a40f882cf599 --- /dev/null +++ b/experimental/framework/tree-react-api/.npmignore @@ -0,0 +1,6 @@ +nyc +*.log +**/*.tsbuildinfo +src/test +dist/test +**/_api-extractor-temp/** diff --git a/experimental/framework/tree-react-api/CHANGELOG.md b/experimental/framework/tree-react-api/CHANGELOG.md new file mode 100644 index 000000000000..0d27a6ab6a03 --- /dev/null +++ b/experimental/framework/tree-react-api/CHANGELOG.md @@ -0,0 +1 @@ +# @fluid-experimental/tree-react-api diff --git a/experimental/framework/tree-react-api/LICENSE b/experimental/framework/tree-react-api/LICENSE new file mode 100644 index 000000000000..60af0a6a40e9 --- /dev/null +++ b/experimental/framework/tree-react-api/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/experimental/framework/tree-react-api/README.md b/experimental/framework/tree-react-api/README.md new file mode 100644 index 000000000000..38a4a39fac50 --- /dev/null +++ b/experimental/framework/tree-react-api/README.md @@ -0,0 +1,91 @@ +# @fluid-experimental/tree-react-api + +Utilities for using SharedTree with React. + +This package aims to assist SharedTree based React applications handle some common cases by providing code such applications can share. +This should improve the quality of such applications by allowing them to share and improve a single implementation of this logic +(for example ensuring they all handle out of schema documents properly), while reducing the need for boilerplate. + +## Known Issues and Limitations + +These are a mix of issues that were encountered when authoring this package, as well as limitations of this package. + +Some of this logic would be useful for non-react applications: to avoid creating even more septate packages, that logic is not split into its own package. +If there is clear demand for this to be done, it might be done in the future. + +There is no service implementation agnostic client abstraction that can be referred to here (ex: shared by TinyliciousClient, AzureClient and OdspClient). +This makes documenting compatibility with that implicit common API difficult. +It also makes writing service agnostic code at that abstraction level harder. + +There does not appear to be a local service implementation of the above mentioned abstraction, which makes testing the code in the package harder. + +The commonly used boilerplate for setting up a ContainerSchema based application configures the dev-tools, +but currently can't be included in this package due to dependency layering issues. + + + + + + +**IMPORTANT: This package is experimental.** +**Its APIs may change without notice.** + +**Do not use in production scenarios.** + +## 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 `^`. + +## Installation + +To get started, install the package by running the following command: + +```bash +npm i @fluid-experimental/tree-react-api +``` + +## API Documentation + +API documentation for **@fluid-experimental/tree-react-api** is available at . + +## Contribution Guidelines + +There are many ways to [contribute](https://github.com/microsoft/FluidFramework/blob/main/CONTRIBUTING.md) to Fluid. + +- Participate in Q&A in our [GitHub Discussions](https://github.com/microsoft/FluidFramework/discussions). +- [Submit bugs](https://github.com/microsoft/FluidFramework/issues) and help us verify fixes as they are checked in. +- Review the [source code changes](https://github.com/microsoft/FluidFramework/pulls). +- [Contribute bug fixes](https://github.com/microsoft/FluidFramework/blob/main/CONTRIBUTING.md). + +Detailed instructions for working in the repo can be found in the [Wiki](https://github.com/microsoft/FluidFramework/wiki). + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +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/trademarks). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. + +## Help + +Not finding what you're looking for in this README? Check out our [GitHub +Wiki](https://github.com/microsoft/FluidFramework/wiki) or [fluidframework.com](https://fluidframework.com/docs/). + +Still not finding what you're looking for? Please [file an +issue](https://github.com/microsoft/FluidFramework/wiki/Submitting-Bugs-and-Feature-Requests). + +Thank you! + +## 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/experimental/framework/tree-react-api/api-extractor-lint.json b/experimental/framework/tree-react-api/api-extractor-lint.json new file mode 100644 index 000000000000..03e514c83199 --- /dev/null +++ b/experimental/framework/tree-react-api/api-extractor-lint.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../../common/build/build-common/api-extractor-lint.esm.primary.json" +} diff --git a/experimental/framework/tree-react-api/api-extractor.json b/experimental/framework/tree-react-api/api-extractor.json new file mode 100644 index 000000000000..220318653e0f --- /dev/null +++ b/experimental/framework/tree-react-api/api-extractor.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../../common/build/build-common/api-extractor-base.esm.primary.json" +} diff --git a/experimental/framework/tree-react-api/api-report/tree-react-api.api.md b/experimental/framework/tree-react-api/api-report/tree-react-api.api.md new file mode 100644 index 000000000000..a353d6c6bab5 --- /dev/null +++ b/experimental/framework/tree-react-api/api-report/tree-react-api.api.md @@ -0,0 +1,56 @@ +## API Report File for "@fluid-experimental/tree-react-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { DataObject } from '@fluidframework/aqueduct/internal'; +import type { DataObjectClass } from '@fluidframework/fluid-static'; +import type { IFluidDataStoreFactory } from '@fluidframework/runtime-definitions/internal'; +import type { IFluidLoadable } from '@fluidframework/core-interfaces'; +import { ImplicitFieldSchema } from '@fluidframework/tree/internal'; +import * as React_2 from 'react'; +import { SchemaIncompatible } from '@fluidframework/tree/internal'; +import { TreeConfiguration } from '@fluidframework/tree/internal'; +import { TreeFieldFromImplicitField } from '@fluidframework/tree/internal'; +import { TreeNode } from '@fluidframework/tree'; +import { TreeView } from '@fluidframework/tree/internal'; + +// @public +export interface IReactTreeDataObject extends ITreeDataObject { + readonly TreeViewComponent: (props: TreeViewProps) => React_2.JSX.Element; +} + +// @public +export interface ITreeDataObject { + readonly config: TreeConfiguration; + readonly key: string; + readonly tree: TreeView; +} + +// @public +export interface SchemaIncompatibleProps { + readonly error: SchemaIncompatible; + readonly upgradeSchema: () => void; +} + +// @public +export function treeDataObject(key: string, treeConfiguration: TreeConfiguration): DataObjectClass & IFluidLoadable>; + +// @internal +export function treeDataObjectInternal(key: string, treeConfiguration: TreeConfiguration): DataObjectClass & IFluidLoadable & DataObject> & { + readonly factory: IFluidDataStoreFactory; +}; + +// @public +export interface TreeViewProps { + readonly errorComponent?: React_2.FC; + readonly viewComponent: React_2.FC<{ + root: TreeFieldFromImplicitField; + }>; +} + +// @public +export function useTree(subtreeRoot: TreeNode): void; + +``` diff --git a/experimental/framework/tree-react-api/package.json b/experimental/framework/tree-react-api/package.json new file mode 100644 index 000000000000..c09f9d47f11e --- /dev/null +++ b/experimental/framework/tree-react-api/package.json @@ -0,0 +1,132 @@ +{ + "name": "@fluid-experimental/tree-react-api", + "version": "2.0.0-rc.4.0.0", + "description": "Experimental SharedTree API for React integration.", + "homepage": "https://fluidframework.com", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/FluidFramework.git", + "directory": "experimental/framework/tree-react-api" + }, + "license": "MIT", + "author": "Microsoft and contributors", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "import": { + "types": "./lib/public.d.ts", + "default": "./lib/index.js" + }, + "require": { + "types": "./dist/public.d.ts", + "default": "./dist/index.js" + } + }, + "./internal": { + "import": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "main": "dist/index.js", + "types": "./dist/public.d.ts", + "scripts": { + "api": "fluid-build . --task api", + "api-extractor:commonjs": "flub generate entrypoints --outDir ./dist", + "api-extractor:esnext": "flub generate entrypoints --outDir ./lib", + "build": "fluid-build . --task build", + "build:commonjs": "fluid-build . --task commonjs", + "build:compile": "fluid-build . --task compile", + "build:docs": "api-extractor run --local", + "build:esnext": "tsc --project ./tsconfig.json && copyfiles -f ../../../common/build/build-common/src/esm/package.json ./lib", + "build:test": "npm run build:test:esm && npm run build:test:cjs", + "build:test:cjs": "fluid-tsc commonjs --project ./src/test/tsconfig.cjs.json", + "build:test:esm": "tsc --project ./src/test/tsconfig.json", + "check:are-the-types-wrong": "attw --pack . --entrypoints .", + "check:prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore", + "check:release-tags": "api-extractor run --local --config ./api-extractor-lint.json", + "ci:build:docs": "api-extractor run", + "clean": "rimraf --glob dist lib \"*.d.ts\" \"**/*.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": "fluid-build --task format .", + "format:prettier": "prettier --write . --cache --ignore-path ../../../.prettierignore", + "lint": "fluid-build . --task lint", + "lint:fix": "fluid-build . --task eslint:fix --task format", + "postpack": "tar -cf ./tree-react-api.test-files.tar ./src/test ./dist/test ./lib/test", + "test": "npm run test:mocha", + "test:coverage": "c8 npm test", + "test:mocha": "npm run test:mocha:esm && echo skipping cjs to avoid overhead - npm run test:mocha:cjs", + "test:mocha:cjs": "cross-env MOCHA_SPEC=dist/test mocha", + "test:mocha:esm": "mocha", + "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha", + "tsc": "fluid-tsc commonjs --project ./tsconfig.cjs.json && copyfiles -f ../../../common/build/build-common/src/cjs/package.json ./dist", + "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/aqueduct": "workspace:~", + "@fluidframework/core-interfaces": "workspace:~", + "@fluidframework/datastore-definitions": "workspace:~", + "@fluidframework/fluid-static": "workspace:~", + "@fluidframework/runtime-definitions": "workspace:~", + "@fluidframework/tree": "workspace:~", + "react": "^17.0.1" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.15.2", + "@biomejs/biome": "^1.6.2", + "@fluid-internal/mocha-test-setup": "workspace:~", + "@fluid-tools/build-cli": "0.38.0-259537", + "@fluidframework/build-common": "^2.0.3", + "@fluidframework/build-tools": "0.38.0-259537", + "@fluidframework/eslint-config-fluid": "^5.1.0", + "@fluidframework/tinylicious-client": "workspace:~", + "@microsoft/api-extractor": "^7.42.3", + "@types/mocha": "^9.1.1", + "@types/node": "^18.19.0", + "@types/react": "^17.0.44", + "c8": "^8.0.1", + "copyfiles": "^2.4.1", + "cross-env": "^7.0.3", + "eslint": "~8.55.0", + "eslint-config-prettier": "~9.0.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" + }, + "typeValidation": { + "disabled": true, + "broken": {} + } +} diff --git a/experimental/framework/tree-react-api/prettier.config.cjs b/experimental/framework/tree-react-api/prettier.config.cjs new file mode 100644 index 000000000000..d4870022599f --- /dev/null +++ b/experimental/framework/tree-react-api/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/experimental/framework/tree-react-api/src/index.ts b/experimental/framework/tree-react-api/src/index.ts new file mode 100644 index 000000000000..a4ddd8be34d0 --- /dev/null +++ b/experimental/framework/tree-react-api/src/index.ts @@ -0,0 +1,18 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +/** + * Utilities for using SharedTree with React. + * @packageDocumentation + */ + +export type { + ITreeDataObject, + IReactTreeDataObject, + TreeViewProps, + SchemaIncompatibleProps, +} from "./reactSharedTreeView.js"; +export { treeDataObject, treeDataObjectInternal } from "./reactSharedTreeView.js"; +export { useTree } from "./useTree.js"; diff --git a/examples/data-objects/inventory-app/src/reactSharedTreeView.tsx b/experimental/framework/tree-react-api/src/reactSharedTreeView.tsx similarity index 58% rename from examples/data-objects/inventory-app/src/reactSharedTreeView.tsx rename to experimental/framework/tree-react-api/src/reactSharedTreeView.tsx index 6f85626fb452..ddc1b2eb5b3b 100644 --- a/examples/data-objects/inventory-app/src/reactSharedTreeView.tsx +++ b/experimental/framework/tree-react-api/src/reactSharedTreeView.tsx @@ -3,34 +3,28 @@ * Licensed under the MIT License. */ -// Lint rule can be disabled once eslint config is upgraded to 5.3.0+ -// eslint-disable-next-line import/no-internal-modules -import { DataObject } from "@fluidframework/aqueduct/internal"; -import { IFluidHandle, type IFluidLoadable } from "@fluidframework/core-interfaces"; -import { IChannelFactory } from "@fluidframework/datastore-definitions"; +/* eslint-disable unicorn/no-null */ + +import { DataObject, DataObjectFactory } from "@fluidframework/aqueduct/internal"; +import type { IFluidHandle, IFluidLoadable } from "@fluidframework/core-interfaces"; +import type { IChannelFactory } from "@fluidframework/datastore-definitions"; +import type { DataObjectClass } from "@fluidframework/fluid-static"; +import type { IFluidDataStoreFactory } from "@fluidframework/runtime-definitions/internal"; import { configuredSharedTree, typeboxValidator, - // eslint-disable-next-line import/no-internal-modules -} from "@fluidframework/tree/internal"; -import { - type DataObjectClass, - ITree, + type ITree, + type SchemaIncompatible, + type TreeConfiguration, + type TreeFieldFromImplicitField, + type TreeView, type ImplicitFieldSchema, - SchemaIncompatible, - TreeConfiguration, - TreeFieldFromImplicitField, - TreeView, -} from "fluid-framework"; +} from "@fluidframework/tree/internal"; import * as React from "react"; -/** - * This file contains logic not specific to this particular sample that other apps may want to use. - * Eventually this should be published as part of a package apps can use. - */ - /** * Opt into extra validation to detect encoding bugs and data corruption. + * As long as this is an experimental package, opting into extra validation (at a small perf and bundle size cost) seems reasonable. */ const SharedTree = configuredSharedTree({ jsonValidator: typeboxValidator, @@ -43,32 +37,51 @@ const SharedTree = configuredSharedTree({ export const factory: IChannelFactory = SharedTree.getFactory(); /** - * Object with the required recursive type to be used to mark DataObjectClasses. - * Note that this probably doesn't work when exported to a package API as it will likely infer `any`. - * TODO: Simplify DataObjectClasses to avoid needing this. + * Defines a DataObject for a {@link @fluidframework/tree#SharedTree} with a built in {@link @fluidframework/tree#TreeConfiguration}. + * @param key - See {@link ITreeDataObject.key}. + * @param treeConfiguration - See {@link ITreeDataObject.config}. + * @returns A {@link @fluidframework/fluid-static#DataObjectClass} to allow easy use of a SharedTree in a ContainerSchema. + * @public */ -const dataObjectFactoryMarker = { - get IFluidDataStoreFactory(): typeof dataObjectFactoryMarker { - return this; - }, -}; +export function treeDataObject( + key: string, + treeConfiguration: TreeConfiguration, +): DataObjectClass & IFluidLoadable> { + return treeDataObjectInternal(key, treeConfiguration); +} /** * Defines a DataObject for a {@link @fluidframework/tree#SharedTree} with a built in {@link @fluidframework/tree#TreeConfiguration}. * @param key - See {@link ITreeDataObject.key}. * @param treeConfiguration - See {@link ITreeDataObject.config}. - * @returns A {@link @fluidframework/fluid-static#DataObjectClass} to allow easy use of a SharedTree in a ContainerSchema + * @returns A {@link @fluidframework/fluid-static#DataObjectClass} to allow easy use of a SharedTree in a ContainerSchema. + * @internal */ -export function treeDataObject( +export function treeDataObjectInternal( key: string, treeConfiguration: TreeConfiguration, -): DataObjectClass & IFluidLoadable> { - return class InventoryList extends TreeDataObject { +): DataObjectClass & IFluidLoadable & DataObject> & { + readonly factory: IFluidDataStoreFactory; +} { + return class SchemaAwareTreeDataObject extends TreeDataObject { public readonly key = key; public readonly config = treeConfiguration; + + public static readonly factory = new DataObjectFactory( + `TreeDataObject:${key}`, + SchemaAwareTreeDataObject, + [SharedTree.getFactory()], + {}, + ); }; } +/** + * A schema-aware Tree DataObject. + * @remarks + * Allows for the Tree's schema to be baked into the container schema. + * @public + */ export interface ITreeDataObject { /** * The key under the root DataObject in which the {@link @fluidframework/tree#SharedTree} is stored. @@ -90,6 +103,18 @@ export interface ITreeDataObject { */ readonly config: TreeConfiguration; + /** + * The TreeView. + */ + readonly tree: TreeView; +} + +/** + * {@link ITreeDataObject} extended with a React Component to view the tree. + * @public + */ +export interface IReactTreeDataObject + extends ITreeDataObject { /** * React component which handles schematizing trees. * This includes displaying errors when the document can not be schematized. @@ -99,22 +124,35 @@ export interface ITreeDataObject { * and thus making it a member avoids the user of this from having to explicitly provide the type parameter. * This is an arrow function not a method so it gets the correct this when not called as a member. */ - readonly TreeViewComponent: ({ - viewComponent, - }: { - viewComponent: React.FC<{ root: TreeFieldFromImplicitField }>; - }) => React.JSX.Element; + readonly TreeViewComponent: (props: TreeViewProps) => React.JSX.Element; +} + +/** + * React props for viewing a tree. + * @public + */ +export interface TreeViewProps { + /** + * Component to display the tree content. + */ + readonly viewComponent: React.FC<{ root: TreeFieldFromImplicitField }>; + /** + * Component to display instead of the {@link TreeViewProps.viewComponent} + * when tree content is not compatible with the {@link @fluidframework/tree#TreeConfiguration}. + * + * @defaultValue Component which describes the situation (in English) and allows the user to upgrade the schema to match the {@link @fluidframework/tree#TreeConfiguration} if possible. + */ + readonly errorComponent?: React.FC; } /** * Generic DataObject for shared trees. + * @internal */ export abstract class TreeDataObject extends DataObject - implements ITreeDataObject + implements IReactTreeDataObject { - public static readonly factory = dataObjectFactoryMarker; - #tree?: TreeView; public get tree(): TreeView { @@ -122,7 +160,7 @@ export abstract class TreeDataObject { const tree = this.runtime.createChannel(undefined, factory.type) as ITree; this.#tree = tree.schematize(this.config); // Initialize the tree content and schema. @@ -130,16 +168,17 @@ export abstract class TreeDataObject { const handle = this.root.get>(this.key); if (handle === undefined) throw new Error("map should be populated on creation by 'initializingFirstTime'"); // If the tree is incompatible with the config's schema, // the TreeView exposes an error which is explicitly handled by TreeViewComponent. - this.#tree = (await handle.get()).schematize(this.config); + const tree = await handle.get(); + this.#tree = tree.schematize(this.config); } - protected async hasInitialized() { + protected override async hasInitialized(): Promise { if (this.#tree === undefined) throw new Error(this.getUninitializedErrorString("tree")); } @@ -147,14 +186,15 @@ export abstract class TreeDataObject; + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/explicit-function-return-type public readonly TreeViewComponent = ({ viewComponent, - }: { - viewComponent: React.FC<{ root: TreeFieldFromImplicitField }>; - }) => + errorComponent, + }: TreeViewProps) => TreeViewComponent({ tree: this, viewComponent, + errorComponent, }); } @@ -162,17 +202,13 @@ export abstract class TreeDataObject({ tree, viewComponent, errorComponent, -}: { +}: TreeViewProps & { tree: TreeDataObject; - viewComponent: React.FC<{ root: TreeFieldFromImplicitField }>; - errorComponent?: React.FC<{ - error: SchemaIncompatible; - upgradeSchema: () => void; - }>; }) { const view = tree.tree; @@ -182,14 +218,14 @@ function TreeViewComponent({ React.useEffect(() => { let ignore = false; - const update = () => { + const update = (): void => { if (!ignore) { - if (view.error !== undefined) { - setError(view.error); - setRoot(null); - } else { + if (view.error === undefined) { setError(null); setRoot(view.root); + } else { + setError(view.error); + setRoot(null); } } }; @@ -197,7 +233,7 @@ function TreeViewComponent({ update(); view.events.on("rootChanged", update); - return () => { + return (): void => { ignore = true; // View is owned by tree so its not disposed here. }; @@ -217,16 +253,26 @@ function TreeViewComponent({ return React.createElement(viewComponent, { root }); } +/** + * React Props for displaying when the opened document is incompatible with the required view schema. + * @public + */ +export interface SchemaIncompatibleProps { + /** + * Information about how the schema is incompatible. + */ + readonly error: SchemaIncompatible; + /** + * Callback to request that the stored schema in the document be upgraded. + */ + readonly upgradeSchema: () => void; +} + /** * React component which displays schema errors and allows upgrading schema when possible. */ -function TreeErrorComponent({ - error, - upgradeSchema, -}: { - error: SchemaIncompatible; - upgradeSchema: () => void; -}) { +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +function TreeErrorComponent({ error, upgradeSchema }: SchemaIncompatibleProps) { // eslint-disable-next-line unicorn/prefer-ternary if (error.canUpgrade) { return ( @@ -236,7 +282,7 @@ function TreeErrorComponent({ document format can be updated. This may prevent other versions of the application from opening this document. - ; + ; ); } else { diff --git a/experimental/framework/tree-react-api/src/test/reactSharedTreeView.spec.ts b/experimental/framework/tree-react-api/src/test/reactSharedTreeView.spec.ts new file mode 100644 index 000000000000..5376145170aa --- /dev/null +++ b/experimental/framework/tree-react-api/src/test/reactSharedTreeView.spec.ts @@ -0,0 +1,43 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { strict as assert } from "node:assert"; + +import { SchemaFactory, TreeConfiguration } from "@fluidframework/tree"; +import { TinyliciousClient } from "@fluidframework/tinylicious-client/internal"; +import type { ContainerSchema } from "@fluidframework/fluid-static"; +import { treeDataObject } from "../reactSharedTreeView.js"; + +describe("reactSharedTreeView", () => { + it("treeDataObject", async () => { + const builder = new SchemaFactory("tree-react-api"); + + class Inventory extends builder.object("Contoso:InventoryItem-1.0.0", { + nuts: builder.number, + bolts: builder.number, + }) {} + + const containerSchema = { + initialObjects: { + // TODO: it seems odd that DataObjects in container schema need both a key under initialObjects where they are, + // as well as a key under the root data object, and SharedObjects only need one key. + // Maybe we can default the shared object's key to be derived from the data objects key by default? + tree: treeDataObject( + "tree", + new TreeConfiguration(Inventory, () => ({ nuts: 5, bolts: 6 })), + ), + }, + } satisfies ContainerSchema; + + // TODO: Ideally we would use a local-server service-client, but one does not appear to exist. + const tinyliciousClient = new TinyliciousClient(); + + const { container } = await tinyliciousClient.createContainer(containerSchema); + const tree = container.initialObjects.tree; + assert.equal(tree.tree.root.nuts, 5); + tree.tree.root.nuts += 1; + assert.equal(tree.tree.root.bolts, 6); + }); +}); diff --git a/experimental/framework/tree-react-api/src/test/tsconfig.cjs.json b/experimental/framework/tree-react-api/src/test/tsconfig.cjs.json new file mode 100644 index 000000000000..fdd3a3c1c9fc --- /dev/null +++ b/experimental/framework/tree-react-api/src/test/tsconfig.cjs.json @@ -0,0 +1,12 @@ +{ + // This config must be used in a "type": "commonjs" environment. (Use `fluid-tsc commonjs`.) + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/test", + }, + "references": [ + { + "path": "../../tsconfig.cjs.json", + }, + ], +} diff --git a/experimental/framework/tree-react-api/src/test/tsconfig.json b/experimental/framework/tree-react-api/src/test/tsconfig.json new file mode 100644 index 000000000000..77c14e7f1f56 --- /dev/null +++ b/experimental/framework/tree-react-api/src/test/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../../common/build/build-common/tsconfig.test.node16.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "../../lib/test", + "types": ["mocha", "node"], + }, + "include": ["./**/*"], + "references": [ + { + "path": "../..", + }, + ], +} diff --git a/experimental/framework/tree-react-api/src/useTree.ts b/experimental/framework/tree-react-api/src/useTree.ts new file mode 100644 index 000000000000..299f0fcd9353 --- /dev/null +++ b/experimental/framework/tree-react-api/src/useTree.ts @@ -0,0 +1,30 @@ +/*! + * Copyright (c) Microsoft Corporation and contributors. All rights reserved. + * Licensed under the MIT License. + */ + +import { Tree, type TreeNode } from "@fluidframework/tree"; +import * as React from "react"; + +/** + * Custom hook which invalidates a React Component when there is a change in the subtree defined by `subtreeRoot`. + * This includes changes to the tree's content, but not changes to its parentage. + * See {@link @fluidframework/tree#TreeChangeEvents.treeChanged} for details. + * @privateRemarks + * Without a way to get invalidation callbacks for specific fields, + * it's impractical to implement an ergonomic and efficient more fine-grained invalidation hook. + * @public + */ +export function useTree(subtreeRoot: TreeNode): void { + // Use a React effect hook to invalidate this component when the subtreeRoot changes. + // We do this by incrementing a counter, which is passed as a dependency to the effect hook. + const [invalidations, setInvalidations] = React.useState(0); + + // React effect hook that increments the 'invalidation' counter whenever subtreeRoot or any of its children change. + React.useEffect(() => { + // Returns the cleanup function to be invoked when the component unmounts. + return Tree.on(subtreeRoot, "treeChanged", () => { + setInvalidations((i) => i + 1); + }); + }, [invalidations, subtreeRoot]); +} diff --git a/experimental/framework/tree-react-api/tsconfig.cjs.json b/experimental/framework/tree-react-api/tsconfig.cjs.json new file mode 100644 index 000000000000..6ec35ad731a3 --- /dev/null +++ b/experimental/framework/tree-react-api/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + // This config must be used in a "type": "commonjs" environment. (Use `fluid-tsc commonjs`.) + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + }, +} diff --git a/experimental/framework/tree-react-api/tsconfig.json b/experimental/framework/tree-react-api/tsconfig.json new file mode 100644 index 000000000000..dd5ebc442f49 --- /dev/null +++ b/experimental/framework/tree-react-api/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../common/build/build-common/tsconfig.node16.json", + "include": ["src/**/*"], + "exclude": ["src/test/**/*"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + }, +} diff --git a/feeds/internal-build.txt b/feeds/internal-build.txt index 2567945b444c..e8c5677266c1 100644 --- a/feeds/internal-build.txt +++ b/feeds/internal-build.txt @@ -69,6 +69,7 @@ fluid-framework @fluidframework/core-interfaces @fluidframework/container-definitions @fluid-internal/client-utils +@fluid-experimental/tree-react-api @fluid-experimental/last-edited @fluid-experimental/data-objects @fluid-experimental/tree diff --git a/feeds/internal-test.txt b/feeds/internal-test.txt index 000d556c3446..6f7a6f24a2f4 100644 --- a/feeds/internal-test.txt +++ b/feeds/internal-test.txt @@ -76,6 +76,7 @@ fluid-framework @fluidframework/core-interfaces @fluidframework/container-definitions @fluid-internal/client-utils +@fluid-experimental/tree-react-api @fluid-experimental/last-edited @fluid-experimental/data-objects @fluid-experimental/tree diff --git a/feeds/public.txt b/feeds/public.txt index 5133e2a89358..9c5e5783b023 100644 --- a/feeds/public.txt +++ b/feeds/public.txt @@ -67,6 +67,7 @@ fluid-framework @fluidframework/core-interfaces @fluidframework/container-definitions @fluid-internal/client-utils +@fluid-experimental/tree-react-api @fluid-experimental/last-edited @fluid-experimental/data-objects @fluid-experimental/tree diff --git a/packages/framework/fluid-static/src/types.ts b/packages/framework/fluid-static/src/types.ts index b1f9f4bd2ac7..5924b94880e9 100644 --- a/packages/framework/fluid-static/src/types.ts +++ b/packages/framework/fluid-static/src/types.ts @@ -46,11 +46,16 @@ export type LoadableObjectClass = * * @typeParam T - The class of the `DataObject`. * @privateRemarks - * Having both `factory` and `LoadableObjectCtor` is redundant, and having `factory` not actually work as a factory is also strange. - * This may need some refinement. + * Having both `factory` and `LoadableObjectCtor` is redundant. + * TODO: It appears the factory is what's used, so the constructor should be removed. * @public */ export type DataObjectClass = { + /** + * @privateRemarks + * This has to implement {@link @fluidframework/runtime-definitions#IFluidDataStoreFactory}. + * TODO: Gain type safety for this without leaking IFluidDataStoreFactory as public using type erasure. + */ readonly factory: { readonly IFluidDataStoreFactory: DataObjectClass["factory"] }; // eslint-disable-next-line @typescript-eslint/no-explicit-any } & (new (...args: any[]) => T); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2491f8336f26..8ee69f07d150 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1879,6 +1879,7 @@ importers: '@biomejs/biome': ^1.6.2 '@fluid-example/example-utils': workspace:~ '@fluid-example/webpack-fluid-loader': workspace:~ + '@fluid-experimental/tree-react-api': workspace:~ '@fluidframework/aqueduct': workspace:~ '@fluidframework/build-common': ^2.0.3 '@fluidframework/build-tools': 0.38.0-259537 @@ -1913,6 +1914,7 @@ importers: webpack-merge: ^5.8.0 dependencies: '@fluid-example/example-utils': link:../../utils/example-utils + '@fluid-experimental/tree-react-api': link:../../../experimental/framework/tree-react-api '@fluidframework/aqueduct': link:../../../packages/framework/aqueduct '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions @@ -5360,6 +5362,73 @@ importers: rimraf: 4.4.1 typescript: 5.1.6 + experimental/framework/tree-react-api: + specifiers: + '@arethetypeswrong/cli': ^0.15.2 + '@biomejs/biome': ^1.6.2 + '@fluid-internal/mocha-test-setup': workspace:~ + '@fluid-tools/build-cli': 0.38.0-259537 + '@fluidframework/aqueduct': workspace:~ + '@fluidframework/build-common': ^2.0.3 + '@fluidframework/build-tools': 0.38.0-259537 + '@fluidframework/core-interfaces': workspace:~ + '@fluidframework/datastore-definitions': workspace:~ + '@fluidframework/eslint-config-fluid': ^5.1.0 + '@fluidframework/fluid-static': workspace:~ + '@fluidframework/runtime-definitions': workspace:~ + '@fluidframework/tinylicious-client': workspace:~ + '@fluidframework/tree': workspace:~ + '@microsoft/api-extractor': ^7.42.3 + '@types/mocha': ^9.1.1 + '@types/node': ^18.19.0 + '@types/react': ^17.0.44 + c8: ^8.0.1 + copyfiles: ^2.4.1 + cross-env: ^7.0.3 + eslint: ~8.55.0 + eslint-config-prettier: ~9.0.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 + react: ^17.0.1 + rimraf: ^4.4.0 + typescript: ~5.1.6 + dependencies: + '@fluidframework/aqueduct': link:../../../packages/framework/aqueduct + '@fluidframework/core-interfaces': link:../../../packages/common/core-interfaces + '@fluidframework/datastore-definitions': link:../../../packages/runtime/datastore-definitions + '@fluidframework/fluid-static': link:../../../packages/framework/fluid-static + '@fluidframework/runtime-definitions': link:../../../packages/runtime/runtime-definitions + '@fluidframework/tree': link:../../../packages/dds/tree + react: 17.0.2 + devDependencies: + '@arethetypeswrong/cli': 0.15.2 + '@biomejs/biome': 1.6.2 + '@fluid-internal/mocha-test-setup': link:../../../packages/test/mocha-test-setup + '@fluid-tools/build-cli': 0.38.0-259537_@types+node@18.19.1 + '@fluidframework/build-common': 2.0.3 + '@fluidframework/build-tools': 0.38.0-259537_@types+node@18.19.1 + '@fluidframework/eslint-config-fluid': 5.1.0_bpztyfltmpuv6lhsgzfwtmxhte + '@fluidframework/tinylicious-client': link:../../../packages/service-clients/tinylicious-client + '@microsoft/api-extractor': 7.42.3_f66stvskxun56mencgf6l5564y_@types+node@18.19.1 + '@types/mocha': 9.1.1 + '@types/node': 18.19.1 + '@types/react': 17.0.71 + c8: 8.0.1 + copyfiles: 2.4.1 + cross-env: 7.0.3 + eslint: 8.55.0 + eslint-config-prettier: 9.0.0_eslint@8.55.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/common/client-utils: specifiers: '@arethetypeswrong/cli': ^0.15.2 @@ -19355,13 +19424,6 @@ packages: /@jridgewell/sourcemap-codec/1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /@jridgewell/trace-mapping/0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - /@jridgewell/trace-mapping/0.3.25: resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: @@ -43049,7 +43111,7 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 dev: true