From d9bfcb60d1e719e0ac9b5e1147a3e15074f389e6 Mon Sep 17 00:00:00 2001 From: Nathan Hardy Date: Thu, 7 Dec 2023 15:17:48 +1100 Subject: [PATCH] kerosene-ui: Add boundaries for react-query integration (#118) --- .eslintrc.js | 2 +- .gitignore | 1 + babel-register.js | 3 +- package.json | 2 +- packages/kerosene-feature-flags/package.json | 1 + .../tsconfig.build.json | 1 + packages/kerosene-test/package.json | 2 +- .../src/react/createStubComponent.tsx | 2 +- packages/kerosene-ui/.npmignore | 4 + packages/kerosene-ui/config/tsconfig.json | 2 +- packages/kerosene-ui/package.json | 38 ++- packages/kerosene-ui/readme.md | 30 +++ packages/kerosene-ui/rollup-config.ts | 38 +-- .../Boundaries/QueriesBoundary.spec.tsx | 200 ++++++++++++++++ .../components/Boundaries/QueriesBoundary.tsx | 170 +++++++++++++ .../Boundaries/QueryBoundary.spec.tsx | 169 +++++++++++++ .../components/Boundaries/QueryBoundary.tsx | 103 ++++++++ .../Boundaries/SuspenseBoundary.spec.tsx | 119 ++++++++++ .../Boundaries/SuspenseBoundary.tsx | 76 ++++++ .../src/components/Boundaries/helpers.ts | 42 ++++ .../src/components/Boundaries/testHelpers.ts | 119 ++++++++++ .../src/components/Boundaries/types.ts | 18 ++ .../{ => components}/ShowWhen/index.spec.tsx | 0 .../src/{ => components}/ShowWhen/index.tsx | 2 +- packages/kerosene-ui/src/index.ts | 2 +- packages/kerosene-ui/src/react-query.ts | 14 ++ packages/kerosene/.npmignore | 4 + packages/kerosene/config/tsconfig.json | 2 +- packages/kerosene/package.json | 17 +- packages/kerosene/readme.md | 6 +- packages/kerosene/rollup-config.ts | 33 +-- .../kerosene/src/error/ExtendableError.ts | 4 +- packages/kerosene/src/promise/Deferred.ts | 5 +- packages/kerosene/src/types/index.ts | 8 +- testSetup.ts | 28 ++- tsconfig.json | 17 +- yarn.lock | 223 ++++++++++++------ 37 files changed, 1358 insertions(+), 149 deletions(-) create mode 100644 packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.spec.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/QueryBoundary.spec.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/QueryBoundary.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.spec.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.tsx create mode 100644 packages/kerosene-ui/src/components/Boundaries/helpers.ts create mode 100644 packages/kerosene-ui/src/components/Boundaries/testHelpers.ts create mode 100644 packages/kerosene-ui/src/components/Boundaries/types.ts rename packages/kerosene-ui/src/{ => components}/ShowWhen/index.spec.tsx (100%) rename packages/kerosene-ui/src/{ => components}/ShowWhen/index.tsx (90%) create mode 100644 packages/kerosene-ui/src/react-query.ts diff --git a/.eslintrc.js b/.eslintrc.js index 00193a7..f423516 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,7 +25,7 @@ module.exports = { }, overrides: [ { - files: ["*.js"], + files: ["*.js", "*.cjs"], rules: { "global-require": "off", strict: "off", diff --git a/.gitignore b/.gitignore index 09eeb43..8620eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .coverage .dist +dist es lib diff --git a/babel-register.js b/babel-register.js index c754d4e..4f5ed33 100644 --- a/babel-register.js +++ b/babel-register.js @@ -7,5 +7,4 @@ require("@babel/register")({ extensions: [".js", ".ts"], }); -require("core-js/features/array/flat"); -require("core-js/features/array/flat-map"); +require("core-js/features/promise/with-resolvers"); diff --git a/package.json b/package.json index ad02aed..2a8420f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "collectCoverageFrom": [ "/packages/**/*.(js|jsx|ts|tsx)", - "!/packages/*/(.dist|es|lib)/**/*", + "!/packages/*/(.dist|dist|es|lib)/**/*", "!**/*.(spec|test).*", "!**/babel.config.js", "!**/rollup.config.js", diff --git a/packages/kerosene-feature-flags/package.json b/packages/kerosene-feature-flags/package.json index 6825425..efc62b4 100644 --- a/packages/kerosene-feature-flags/package.json +++ b/packages/kerosene-feature-flags/package.json @@ -19,6 +19,7 @@ "homepage": "https://github.com/KablamoOSS/kerosene", "private": false, "license": "MIT", + "type": "module", "main": ".dist/index.js", "browser": ".dist/index.js", "directories": { diff --git a/packages/kerosene-feature-flags/tsconfig.build.json b/packages/kerosene-feature-flags/tsconfig.build.json index 8a37772..84a907b 100644 --- a/packages/kerosene-feature-flags/tsconfig.build.json +++ b/packages/kerosene-feature-flags/tsconfig.build.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig", "compilerOptions": { + "target": "ES2015", "declaration": true, "outDir": ".dist", "noEmit": false diff --git a/packages/kerosene-test/package.json b/packages/kerosene-test/package.json index 0699670..0990ad2 100644 --- a/packages/kerosene-test/package.json +++ b/packages/kerosene-test/package.json @@ -46,7 +46,7 @@ "utils" ], "dependencies": { - "@kablamo/kerosene": "^0.0.29", + "@kablamo/kerosene": "^0.0.35", "@types/lodash": "^4.14.202", "@types/sinon": "^17.0.2", "lodash": "^4.17.21", diff --git a/packages/kerosene-test/src/react/createStubComponent.tsx b/packages/kerosene-test/src/react/createStubComponent.tsx index 73bf1b0..bde0195 100644 --- a/packages/kerosene-test/src/react/createStubComponent.tsx +++ b/packages/kerosene-test/src/react/createStubComponent.tsx @@ -80,7 +80,7 @@ export default function createStubComponent< return class extends React.Component { public static displayName = displayName; - public render() { + public override render() { const { children, ...props } = this.props; return ( diff --git a/packages/kerosene-ui/.npmignore b/packages/kerosene-ui/.npmignore index 99f644a..ac74439 100644 --- a/packages/kerosene-ui/.npmignore +++ b/packages/kerosene-ui/.npmignore @@ -1,3 +1,7 @@ __snapshots__ +config +babel.config.js +rollup-config.ts +rollup.config.js **.spec.ts **.spec.tsx diff --git a/packages/kerosene-ui/config/tsconfig.json b/packages/kerosene-ui/config/tsconfig.json index 475ef90..a5703f6 100644 --- a/packages/kerosene-ui/config/tsconfig.json +++ b/packages/kerosene-ui/config/tsconfig.json @@ -5,7 +5,7 @@ "noEmit": false, "declaration": true, "emitDeclarationOnly": true, - "declarationDir": "../lib/" + "declarationDir": "../dist/" }, "include": ["../src/**/*.ts", "../src/**/*.tsx"], "exclude": ["../src/**/*.spec.ts", "../src/**/*.spec.tsx"] diff --git a/packages/kerosene-ui/package.json b/packages/kerosene-ui/package.json index cc9feb6..81db46b 100644 --- a/packages/kerosene-ui/package.json +++ b/packages/kerosene-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kablamo/kerosene-ui", - "version": "0.0.34", + "version": "0.0.35", "repository": "https://github.com/KablamoOSS/kerosene/tree/master/packages/kerosene-ui", "bugs": { "url": "https://github.com/KablamoOSS/kerosene/issues" @@ -8,9 +8,23 @@ "homepage": "https://github.com/KablamoOSS/kerosene", "private": false, "license": "MIT", - "main": "lib/index.js", - "module": "es/index.js", + "main": "dist/index.cjs", + "module": "dist/index.js", "sideEffects": false, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + }, + "./package.json": "./package.json", + "./react-query": { + "import": "./dist/react-query.mjs", + "require": "./dist/react-query.cjs", + "types": "./dist/react-query.d.ts" + } + }, + "types": "dist/index.d.ts", "directories": { "doc": "readme.md" }, @@ -22,7 +36,7 @@ }, "dependencies": { "@babel/runtime": "^7.23.4", - "@kablamo/kerosene": "^0.0.29", + "@kablamo/kerosene": "^0.0.35", "@types/lodash": "^4.14.202", "lodash": "^4.17.21", "use-sync-external-store": "^1.2.0" @@ -31,6 +45,7 @@ "@kablamo/kerosene-test": "^0.0.13", "@rollup/plugin-babel": "^6.0.4", "@sinonjs/fake-timers": "^11.2.2", + "@tanstack/react-query": "^5.12.2", "@testing-library/dom": "^9.3.3", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", @@ -40,17 +55,28 @@ "jest-when": "^3.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.11", "rollup": "^4.5.1", "rollup-plugin-node-resolve": "^5.2.0" }, "peerDependencies": { + "@tanstack/react-query": ">=5.0.0 <6.0.0", "react": ">=16.8.6", - "react-dom": ">=16.8.6" + "react-dom": ">=16.8.6", + "react-error-boundary": ">=3.0.0 <5.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-query": { + "optional": true + }, + "react-error-boundary": { + "optional": true + } }, "scripts": { "build": "npm-run-all -p build:rollup build:typings", "build:rollup": "rollup -c", "build:typings": "tsc -p ./config/tsconfig.json", - "clean": "rimraf es lib" + "clean": "rimraf dist" } } diff --git a/packages/kerosene-ui/readme.md b/packages/kerosene-ui/readme.md index 2887031..bae0438 100644 --- a/packages/kerosene-ui/readme.md +++ b/packages/kerosene-ui/readme.md @@ -176,6 +176,36 @@ Returns `{ capture: true, passive: true }` when capture passive event listeners Returns `{ capture: true }` when capture passive event listeners are supported, otherwise `true`. +## React Query + +We also include some useful helpers and components when working with [React Query][https://react-query.tanstack.com/]. These aren't included in the main entrypoint for `@kablamo/kerosene-ui`, and instead must be imported like this: + +```ts +import {} from "@kablamo/kerosene-ui/react-query"; +``` + +### `isDefinedQueryObserverResult(query)` + +Returns whether a given query has a defined result. This includes the success state of the query, as well as the states for refetch in progress, as well as the refetch error state with stale data. + +### `isQueryObserverLoadingErrorResult(query)` + +Returns whether a given query is in the error state with no defined result. This includes the error state when data is refetching. + +### `isQueryObserverLoadingResult(query)` + +Returns whether a given query is in the loading state with no defined result. This includes the error state when data is refetching. + +### `` + +Utility component for managing the loading and error states for a single React Query query. Specifying the query automatically infers the type of the success state of the query in the render prop children (if used). + +### `` + +Utility component for managing the loading and error states for multiple React Query queries. Specifying the queries automatically infers the types of the success state of the queries in the render prop children (if used). + +### `` + --- kablamo.com.au diff --git a/packages/kerosene-ui/rollup-config.ts b/packages/kerosene-ui/rollup-config.ts index 744863a..3af0739 100644 --- a/packages/kerosene-ui/rollup-config.ts +++ b/packages/kerosene-ui/rollup-config.ts @@ -1,25 +1,17 @@ import babel from "@rollup/plugin-babel"; import path from "path"; -import type { - ModuleFormat, - RollupOptions, - OutputOptions, - ExternalOption, -} from "rollup"; +import type { RollupOptions, ExternalOption } from "rollup"; import resolve from "rollup-plugin-node-resolve"; // eslint-disable-next-line import/no-relative-packages import generateBabelConfig from "../../config/generateBabelConfig"; import packageJson from "./package.json"; -const input = path.join(__dirname, "src", "index.ts"); +const input = [ + path.join(__dirname, "src", "index.ts"), + path.join(__dirname, "src", "react-query.ts"), +]; -const output = (file: string, format: ModuleFormat): OutputOptions => ({ - dir: path.dirname(file), - format, - indent: false, - preserveModules: true, - sourcemap: true, -}); +const outputDir = path.dirname(packageJson.main); const externals = [ packageJson.dependencies, @@ -52,10 +44,22 @@ export default [ { input, output: [ - output(packageJson.main, "commonjs"), - output(packageJson.module, "esm"), + { + entryFileNames: "[name].cjs", + dir: outputDir, + format: "commonjs", + preserveModules: true, + sourcemap: true, + }, + { + entryFileNames: "[name].mjs", + dir: outputDir, + format: "esm", + preserveModules: true, + sourcemap: true, + }, ], external, plugins, }, -] as RollupOptions[]; +] satisfies RollupOptions[]; diff --git a/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.spec.tsx b/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.spec.tsx new file mode 100644 index 0000000..2c3e12c --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.spec.tsx @@ -0,0 +1,200 @@ +import { Deferred, type DistributiveOmit } from "@kablamo/kerosene"; +import { + useQuery, + QueryClientProvider, + QueryClient, + type UseQueryResult, +} from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { noop } from "lodash"; +import * as React from "react"; +import type { FallbackProps } from "react-error-boundary"; +import QueriesBoundary, { + AggregateQueriesError, + type QueriesBoundaryProps, +} from "./QueriesBoundary"; +import { createQueryObserverLoadingErrorResult } from "./testHelpers"; + +const error = new Error("an error"); +const rejectedPromise = Promise.reject(error); +// A .catch() handler must be added synchronously to prevent PromiseRejectionUnhandledWarning +rejectedPromise.catch(noop); + +describe("QueriesBoundary", () => { + it.each([ + { + expected: "Success", + props: { + children: "Success", + errorFallback: <>errorFallback, + }, + value1: "data1", + value2: "data2", + }, + { + expected: "data1,data2", + props: { + children: ([{ data: data1 }, { data: data2 }]) => ( + <> + {data1},{data2} + + ), + errorFallback: <>errorFallback, + }, + value1: "data1", + value2: "data2", + }, + { + expected: "errorFallback", + props: { + children: "Success", + errorFallback: <>errorFallback, + }, + value1: rejectedPromise, + value2: rejectedPromise, + }, + { + expected: "errorFallbackRender", + props: { + children: "Success", + errorFallbackRender: jest + .fn() + .mockImplementation(({ resetErrorBoundary }: FallbackProps) => ( + + )), + }, + value1: rejectedPromise, + value2: rejectedPromise, + }, + { + expected: "ErrorFallbackComponent", + props: { + children: "Success", + ErrorFallbackComponent: jest + .fn() + .mockImplementation(({ resetErrorBoundary }: FallbackProps) => ( + + )), + }, + value1: rejectedPromise, + value2: rejectedPromise, + }, + ] satisfies Array<{ + expected: string; + props: DistributiveOmit< + QueriesBoundaryProps< + readonly [UseQueryResult, UseQueryResult] + >, + "queries" | "loadingFallback" + >; + value1: string | Promise; + value2: string | Promise; + }>)( + "should render a loading state and then $expected", + async ({ expected, props, value1, value2 }) => { + let deferred1 = new Deferred(); + let deferred2 = new Deferred(); + const Component = () => { + const query1 = useQuery({ + queryKey: ["QueriesBoundary", "Component", "query1"], + queryFn: () => deferred1.promise, + }); + const query2 = useQuery({ + queryKey: ["QueriesBoundary", "Component", "query2"], + queryFn: () => deferred2.promise, + }); + return ( + + ); + }; + render(, { + wrapper({ children }) { + return ( + + {children} + + ); + }, + }); + + expect(screen.getByText("Loading")).toBeInTheDocument(); + + deferred1.resolve(value1); + deferred2.resolve(value2); + await waitFor(() => + expect(screen.getByText(expected)).toBeInTheDocument(), + ); + if (props.errorFallbackRender) { + expect(props.errorFallbackRender).toHaveBeenCalledWith({ + error: expect.any(AggregateQueriesError), + resetErrorBoundary: expect.any(Function), + }); + } + + if (props.ErrorFallbackComponent) { + expect(props.ErrorFallbackComponent).toHaveBeenCalledWith( + { + error: expect.any(AggregateQueriesError), + resetErrorBoundary: expect.any(Function), + }, + expect.anything(), + ); + } + + if (props.errorFallbackRender || props.ErrorFallbackComponent) { + deferred1 = new Deferred(); + deferred2 = new Deferred(); + void userEvent.click(screen.getByRole("button")); + await waitFor(() => + expect(screen.getByText("Loading")).toBeInTheDocument(), + ); + } + }, + ); + + it("should throw if no error fallback is provided", () => { + const queries = [ + createQueryObserverLoadingErrorResult(new Error("an error")), + ]; + expect(() => + render( + // @ts-expect-error Testing error scenario with no error fallback + + children + , + { + wrapper({ children }) { + return ( + + {children} + + ); + }, + }, + ), + ).toThrow( + "QueriesBoundary requires either errorFallback, errorFallbackRender, or ErrorFallbackComponent prop", + ); + }); +}); diff --git a/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.tsx b/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.tsx new file mode 100644 index 0000000..c8d6ea9 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/QueriesBoundary.tsx @@ -0,0 +1,170 @@ +import type { + QueryObserverSuccessResult, + UseQueryResult, +} from "@tanstack/react-query"; +import * as React from "react"; +import type { FallbackProps } from "react-error-boundary"; +import { + isQueryObserverLoadingErrorResult, + isQueryObserverLoadingResult, +} from "./helpers"; +import type { ErrorFallbackProps } from "./types"; + +/** + * Provides a type for the children function of `` + * that allows the types of a successful query to be inferred for ease of use + * @example + * ```ts + * type C = ChildrenFn, + * UseQueryResult, + * ]>; + * // Equivalent to + * type C = (queries: readonly [ + * QueryObserverSuccessResult, + * QueryObserverSuccessResult, + * ]) => React.ReactNode; + * ``` + */ +type ChildrenFn> = (queries: { + [Key in keyof TQueries]: TQueries[Key] extends UseQueryResult< + infer TData, + infer TError + > + ? QueryObserverSuccessResult + : TQueries[Key]; +}) => React.ReactNode; + +export type QueriesBoundaryProps< + TQueries extends ReadonlyArray, +> = { + children: React.ReactNode | ChildrenFn; + loadingFallback: React.ReactNode; + queries: TQueries; +} & ErrorFallbackProps; + +export class AggregateQueriesError extends AggregateError {} + +/** + * Utility component for managing the loading and error states for multiple React Query queries. Specifying the queries + * automatically infers the types of the success state of the queries in the render prop children (if used). + * + * There are three mutually exclusive options for specifying an error fallback: + * - `errorFallback` - JSX element + * - `errorFallbackRender` - a function which takes `FallbackProps` and renders JSX + * - `ErrorFallbackComponent` - a React component which takes `FallbackProps` + * + * @example + * ```tsx + * const MyComponent = () => { + * const query1: UseQueryResult<{ items: readonly string[] }> = useMyQuery1(); + * const query2: UseQueryResult<{ balance: number }> = useMyQuery2(); + * + * return ( + * ( + * <> + * Something went wrong.{" "} + * + * + * )} + * loadingFallback={} + * queries={[query1, query2]} + * > + * {([{ data: { items } }, { data: { balance } }]) => ( + * <> + *

+ * Your balance:{" "} + * {new Intl.NumberFormat("en-AU", { style: "currency", currency: "AUD" }).format(balance)} + *

+ *

+ * You have:{" "} + * {new Intl.ListFormat("en", { style: "long", type: "conunction" }).format(data.items)} + *

+ * + * )} + *
+ * ); + * }; + * ``` + * + * @param props + */ +const QueriesBoundary = < + // Including a newline here to fix broken syntax highlighting + const TQueries extends ReadonlyArray, +>({ + children, + errorFallback, + errorFallbackRender, + ErrorFallbackComponent, + loadingFallback, + queries, +}: QueriesBoundaryProps): JSX.Element => { + /** + * If any of the queries are in the error state, combine them into a single `AggregateError` + */ + const error = React.useMemo(() => { + const errors = queries + .filter(isQueryObserverLoadingErrorResult) + .map((query) => query.error); + return errors.length + ? new AggregateQueriesError( + errors, + "One or more queries are in the LoadingErrorResult state:", + ) + : undefined; + }, [queries]); + + /** + * If any of the queries have a status of `"error"`, this will trigger a refetch of each + */ + const resetErrorBoundary = React.useCallback( + () => + queries + .filter( + (query): query is Extract => + query.status === "error", + ) + .forEach((query) => void query.refetch()), + [queries], + ); + + if (queries.some((query) => isQueryObserverLoadingResult(query))) { + return <>{loadingFallback}; + } + + if (error) { + if (React.isValidElement(errorFallback)) { + return <>{errorFallback}; + } + + const errorFallbackProps: FallbackProps = { + error, + resetErrorBoundary, + }; + + if (typeof errorFallbackRender === "function") { + return <>{errorFallbackRender(errorFallbackProps)}; + } + + if (ErrorFallbackComponent) { + return ; + } + + /* istanbul ignore next */ + throw new Error( + "QueriesBoundary requires either errorFallback, errorFallbackRender, or ErrorFallbackComponent prop", + ); + } + + return ( + <> + {typeof children === "function" + ? children(queries as Parameters>[0]) + : children} + + ); +}; + +export default QueriesBoundary; diff --git a/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.spec.tsx b/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.spec.tsx new file mode 100644 index 0000000..bb1360f --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.spec.tsx @@ -0,0 +1,169 @@ +import { Deferred, type DistributiveOmit } from "@kablamo/kerosene"; +import { + useQuery, + QueryClientProvider, + QueryClient, +} from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { noop } from "lodash"; +import * as React from "react"; +import type { FallbackProps } from "react-error-boundary"; +import QueryBoundary, { type QueryBoundaryProps } from "./QueryBoundary"; +import { createQueryObserverLoadingErrorResult } from "./testHelpers"; + +const error = new Error("an error"); +const rejectedPromise = Promise.reject(error); +// A .catch() handler must be added synchronously to prevent PromiseRejectionUnhandledWarning +rejectedPromise.catch(noop); + +describe("QueryBoundary", () => { + it.each([ + { + expected: "Success", + props: { + children: "Success", + errorFallback: <>errorFallback, + }, + value: "data", + }, + { + expected: "data", + props: { + // eslint-disable-next-line react/jsx-no-useless-fragment + children: ({ data }) => <>{data}, + errorFallback: <>errorFallback, + }, + value: "data", + }, + { + expected: "errorFallback", + props: { + children: "Success", + errorFallback: <>errorFallback, + }, + value: rejectedPromise, + }, + { + expected: "errorFallbackRender", + props: { + children: "Success", + errorFallbackRender: jest + .fn() + .mockImplementation(({ resetErrorBoundary }: FallbackProps) => ( + + )), + }, + value: rejectedPromise, + }, + { + expected: "ErrorFallbackComponent", + props: { + children: "Success", + ErrorFallbackComponent: jest + .fn() + .mockImplementation(({ resetErrorBoundary }: FallbackProps) => ( + + )), + }, + value: rejectedPromise, + }, + ] satisfies Array<{ + expected: string; + props: DistributiveOmit; + value: string | Promise; + }>)( + "should render a loading state and then $expected", + async ({ expected, props, value }) => { + let deferred = new Deferred(); + const Component = () => { + const query = useQuery({ + queryKey: ["QueryBoundary", "Component"], + queryFn: () => deferred.promise, + }); + return ( + + ); + }; + render(, { + wrapper({ children }) { + return ( + + {children} + + ); + }, + }); + + expect(screen.getByText("Loading")).toBeInTheDocument(); + + deferred.resolve(value); + await waitFor(() => + expect(screen.getByText(expected)).toBeInTheDocument(), + ); + if (props.errorFallbackRender) { + expect(props.errorFallbackRender).toHaveBeenCalledWith({ + error, + resetErrorBoundary: expect.any(Function), + }); + } + + if (props.ErrorFallbackComponent) { + expect(props.ErrorFallbackComponent).toHaveBeenCalledWith( + { + error, + resetErrorBoundary: expect.any(Function), + }, + expect.anything(), + ); + } + + if (props.errorFallbackRender || props.ErrorFallbackComponent) { + deferred = new Deferred(); + void userEvent.click(screen.getByRole("button")); + await waitFor(() => + expect(screen.getByText("Loading")).toBeInTheDocument(), + ); + } + }, + ); + + it("should throw if no error fallback is provided", () => { + const query = createQueryObserverLoadingErrorResult(new Error("an error")); + expect(() => + render( + // @ts-expect-error Testing error scenario with no error fallback + + children + , + { + wrapper({ children }) { + return ( + + {children} + + ); + }, + }, + ), + ).toThrow( + "QueryBoundary requires either errorFallback, errorFallbackRender, or ErrorFallbackComponent prop", + ); + }); +}); diff --git a/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.tsx b/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.tsx new file mode 100644 index 0000000..f01a569 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/QueryBoundary.tsx @@ -0,0 +1,103 @@ +import type { + DefinedQueryObserverResult, + UseQueryResult, +} from "@tanstack/react-query"; +import * as React from "react"; +import type { FallbackProps } from "react-error-boundary"; +import { isDefinedQueryObserverResult } from "./helpers"; +import type { ErrorFallbackProps } from "./types"; + +export type QueryBoundaryProps = { + children: + | React.ReactNode + | ((query: DefinedQueryObserverResult) => React.ReactNode); + loadingFallback: React.ReactNode; + query: UseQueryResult; +} & ErrorFallbackProps; + +/** + * Utility component for managing the loading and error states for a single React Query query. Specifying the query + * automatically infers the type of the success state of the query in the render prop children (if used). + * + * There are three mutually exclusive options for specifying an error fallback: + * - `errorFallback` - JSX element + * - `errorFallbackRender` - a function which takes `FallbackProps` and renders JSX + * - `ErrorFallbackComponent` - a React component which takes `FallbackProps` + * + * @example + * ```tsx + * const MyComponent = () => { + * const query: UseQueryResult<{ items: readonly string[] }> = useMyQuery(); + * return ( + * ( + * <> + * Something went wrong.{" "} + * + * + * )} + * loadingFallback={} + * query={query} + * > + * {({ data }) => ( + *

+ * You have:{" "} + * {new Intl.ListFormat("en", { style: "long", type: "conunction" }).format(data.items)} + *

+ * )} + *
+ * ); + * }; + * ``` + * + * @param props + */ +const QueryBoundary = < + // Including a newline here to fix broken syntax highlighting + TData = unknown, + TError = unknown, +>({ + children, + errorFallback, + errorFallbackRender, + ErrorFallbackComponent, + loadingFallback, + query, +}: QueryBoundaryProps): JSX.Element => { + const { refetch } = query; + const resetErrorBoundary = React.useCallback(() => { + void refetch(); + }, [refetch]); + + // A "defined" result means that we have usable data + if (isDefinedQueryObserverResult(query)) { + return <>{typeof children === "function" ? children(query) : children}; + } + + if (query.isLoading) { + return <>{loadingFallback}; + } + + if (React.isValidElement(errorFallback)) { + return <>{errorFallback}; + } + + const errorFallbackProps: FallbackProps = { + error: query.error, + resetErrorBoundary, + }; + + if (typeof errorFallbackRender === "function") { + return <>{errorFallbackRender(errorFallbackProps)}; + } + + if (ErrorFallbackComponent) { + return ; + } + + throw new Error( + "QueryBoundary requires either errorFallback, errorFallbackRender, or ErrorFallbackComponent prop", + ); +}; + +export default QueryBoundary; diff --git a/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.spec.tsx b/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.spec.tsx new file mode 100644 index 0000000..1826335 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.spec.tsx @@ -0,0 +1,119 @@ +import { Deferred } from "@kablamo/kerosene"; +// eslint-disable-next-line import/no-extraneous-dependencies +import { + QueryClient, + QueryClientProvider, + useSuspenseQuery, +} from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import { noop } from "lodash"; +import * as React from "react"; +import SuspenseBoundary from "./SuspenseBoundary"; +import type { ErrorFallbackProps } from "./types"; + +const SuspendingComponent = >({ + queryFn, +}: { + queryFn: () => Promise; +}) => { + const query = useSuspenseQuery({ + queryKey: ["SuspenseBoundary", "SuspendingComponent"], + queryFn: () => queryFn(), + }); + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{query.data}; +}; + +const error = new Error("an error"); +const rejectedPromise = Promise.reject(error); +// A .catch() handler must be added synchronously to prevent PromiseRejectionUnhandledWarning +rejectedPromise.catch(noop); + +describe("SuspenseBoundary", () => { + it.each([ + { + expected: "Success", + value: "Success", + }, + { + errorFallbackProps: { + errorFallback: <>errorFallback, + }, + expected: "errorFallback", + value: rejectedPromise, + }, + { + errorFallbackProps: { + errorFallbackRender: jest + .fn() + .mockImplementation(() => <>errorFallbackRender), + }, + expected: "errorFallbackRender", + value: rejectedPromise, + }, + { + errorFallbackProps: { + ErrorFallbackComponent: jest + .fn() + .mockImplementation(() => <>ErrorFallbackComponent), + }, + expected: "ErrorFallbackComponent", + value: rejectedPromise, + }, + ] satisfies Array<{ + errorFallbackProps?: ErrorFallbackProps; + expected: string; + value: string | Promise; + }>)( + "should render a loading state and then $expected", + async ({ + errorFallbackProps = { errorFallback: <>Error }, + expected, + value, + }) => { + const deferred = new Deferred(); + render( + + deferred.promise} /> + , + { + wrapper({ children }) { + return ( + + {children} + + ); + }, + }, + ); + + expect(screen.getByText("Loading")).toBeInTheDocument(); + + deferred.resolve(value); + await waitFor(() => + expect(screen.getByText(expected)).toBeInTheDocument(), + ); + if (errorFallbackProps.errorFallbackRender) { + expect(errorFallbackProps.errorFallbackRender).toHaveBeenCalledWith({ + error, + resetErrorBoundary: expect.any(Function), + }); + } + if (errorFallbackProps.ErrorFallbackComponent) { + expect(errorFallbackProps.ErrorFallbackComponent).toHaveBeenCalledWith( + { + error, + resetErrorBoundary: expect.any(Function), + }, + expect.anything(), + ); + } + }, + ); +}); diff --git a/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.tsx b/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.tsx new file mode 100644 index 0000000..db653c3 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/SuspenseBoundary.tsx @@ -0,0 +1,76 @@ +/* eslint-disable import/no-extraneous-dependencies */ + +import { QueryErrorResetBoundary } from "@tanstack/react-query"; +import * as React from "react"; +import { ErrorBoundary, type ErrorBoundaryProps } from "react-error-boundary"; +import type { ErrorFallbackProps } from "./types"; + +export type SuspenseBoundaryProps = { + children: React.ReactNode; + loadingFallback: React.ReactNode; +} & ErrorFallbackProps; + +/** + * Utility component for managing loading and error states for Suspense components. Also integrates with React Query to + * provide a QueryErrorResetBoundary. + * + * There are three mutually exclusive options for specifying an error fallback: + * - `errorFallback` - JSX element + * - `errorFallbackRender` - a function which takes `FallbackProps` and renders JSX + * - `ErrorFallbackComponent` - a React component which takes `FallbackProps` + * + * @example + * ```tsx + * const SuspendingComponent = () => { + * const query: UseSuspensQueryResult<{ balance: number }> = useMySuspenseQuery(); + * return ( + *

+ * Your balance:{" "} + * {new Intl.NumberFormat("en-AU", { style: "currency", currency: "AUD" }).format(balance)} + *

+ * ); + * }; + * + * const MyComponent = () => ( + * ( + * <> + * Something went wrong.{" "} + * + * + * )} + * loadingFallback={} + * > + * + * + * ); + * ``` + * + * @param props + */ +const SuspenseBoundary = ({ + children, + errorFallback, + errorFallbackRender, + ErrorFallbackComponent, + loadingFallback, +}: SuspenseBoundaryProps) => { + return ( + + {({ reset }) => ( + + {children} + + )} + + ); +}; + +export default SuspenseBoundary; diff --git a/packages/kerosene-ui/src/components/Boundaries/helpers.ts b/packages/kerosene-ui/src/components/Boundaries/helpers.ts new file mode 100644 index 0000000..ea9f4b2 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/helpers.ts @@ -0,0 +1,42 @@ +import type { + DefinedQueryObserverResult, + QueryObserverLoadingErrorResult, + QueryObserverLoadingResult, + UseQueryResult, +} from "@tanstack/react-query"; + +/** + * Returns whether a given query has a defined result. This includes the success state of the query, as well as the + * states for refetch in progress, as well as the refetch error state with stale data. + * @param query React Query query + */ +export function isDefinedQueryObserverResult( + query: UseQueryResult, +): query is DefinedQueryObserverResult { + return query.data !== undefined; +} + +/** + * Returns whether a given query is in the error state with no defined result. This includes the error state when data + * is refetching. + * @param query React Query query + */ +export function isQueryObserverLoadingErrorResult< + TData = unknown, + TError = unknown, +>( + query: UseQueryResult, +): query is QueryObserverLoadingErrorResult { + return query.data === undefined && query.isError; +} + +/** + * Returns whether a given query is in the loading state with no defined result. This includes the error state when data + * is refetching. + * @param query React Query query + */ +export function isQueryObserverLoadingResult( + query: UseQueryResult, +): query is QueryObserverLoadingResult { + return query.data === undefined && query.isLoading; +} diff --git a/packages/kerosene-ui/src/components/Boundaries/testHelpers.ts b/packages/kerosene-ui/src/components/Boundaries/testHelpers.ts new file mode 100644 index 0000000..1f52ecf --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/testHelpers.ts @@ -0,0 +1,119 @@ +import type { + QueryKey, + QueryObserverBaseResult, + QueryObserverLoadingErrorResult, + QueryObserverLoadingResult, + QueryObserverRefetchErrorResult, + QueryObserverSuccessResult, +} from "@tanstack/react-query"; + +function createQueryObserverBaseResult< + TData = unknown, + TError = unknown, +>(): QueryObserverBaseResult & { queryKey: QueryKey } { + return { + data: undefined, + dataUpdatedAt: 0, + error: null, + errorUpdatedAt: 0, + failureCount: 0, + failureReason: null, + errorUpdateCount: 0, + isError: false, + isFetched: false, + isFetchedAfterMount: false, + isFetching: false, + isInitialLoading: false, + isLoading: false, + isLoadingError: false, + isPaused: false, + isPending: true, + isPlaceholderData: false, + isRefetchError: false, + isRefetching: false, + isStale: false, + isSuccess: false, + queryKey: [], + refetch: jest.fn(), + status: "pending", + fetchStatus: "idle", + }; +} + +export function createQueryObserverSuccessResult( + data: TData, +): QueryObserverSuccessResult & { queryKey: QueryKey } { + return { + ...createQueryObserverBaseResult(), + data, + error: null, + isError: false, + isLoading: false, + isLoadingError: false, + isPending: false, + isRefetchError: false, + isSuccess: true, + status: "success", + }; +} + +export function createQueryObserverRefetchErrorResult< + TData = unknown, + TError = unknown, +>( + data: TData, + error: TError, +): QueryObserverRefetchErrorResult & { queryKey: QueryKey } { + return { + ...createQueryObserverBaseResult(), + data, + error, + isError: true, + isLoading: false, + isLoadingError: false, + isPending: false, + isRefetchError: true, + isSuccess: false, + status: "error", + }; +} + +export function createQueryObserverLoadingErrorResult< + TData = unknown, + TError = unknown, +>( + error: TError, +): QueryObserverLoadingErrorResult & { queryKey: QueryKey } { + return { + ...createQueryObserverBaseResult(), + data: undefined, + error, + isError: true, + isLoading: false, + isLoadingError: true, + isPending: false, + isRefetchError: false, + isSuccess: false, + status: "error", + }; +} + +export function createQueryObserverLoadingResult< + TData = unknown, + TError = unknown, +>(): QueryObserverLoadingResult & { queryKey: QueryKey } { + return { + ...createQueryObserverBaseResult(), + data: undefined, + error: null, + isError: false, + isInitialLoading: true, + isLoading: true, + isLoadingError: false, + isPending: true, + isRefetchError: false, + isSuccess: false, + status: "pending", + fetchStatus: "fetching", + }; +} diff --git a/packages/kerosene-ui/src/components/Boundaries/types.ts b/packages/kerosene-ui/src/components/Boundaries/types.ts new file mode 100644 index 0000000..3cb2097 --- /dev/null +++ b/packages/kerosene-ui/src/components/Boundaries/types.ts @@ -0,0 +1,18 @@ +import type { MergedUnion } from "@kablamo/kerosene"; +import type { + ErrorBoundaryPropsWithComponent, + ErrorBoundaryPropsWithFallback, + ErrorBoundaryPropsWithRender, +} from "react-error-boundary"; + +export type ErrorFallbackProps = MergedUnion< + | { + errorFallback: ErrorBoundaryPropsWithFallback["fallback"]; + } + | { + errorFallbackRender: ErrorBoundaryPropsWithRender["fallbackRender"]; + } + | { + ErrorFallbackComponent: ErrorBoundaryPropsWithComponent["FallbackComponent"]; + } +>; diff --git a/packages/kerosene-ui/src/ShowWhen/index.spec.tsx b/packages/kerosene-ui/src/components/ShowWhen/index.spec.tsx similarity index 100% rename from packages/kerosene-ui/src/ShowWhen/index.spec.tsx rename to packages/kerosene-ui/src/components/ShowWhen/index.spec.tsx diff --git a/packages/kerosene-ui/src/ShowWhen/index.tsx b/packages/kerosene-ui/src/components/ShowWhen/index.tsx similarity index 90% rename from packages/kerosene-ui/src/ShowWhen/index.tsx rename to packages/kerosene-ui/src/components/ShowWhen/index.tsx index fb9e660..939802d 100644 --- a/packages/kerosene-ui/src/ShowWhen/index.tsx +++ b/packages/kerosene-ui/src/components/ShowWhen/index.tsx @@ -6,7 +6,7 @@ interface ShowWhenProps { } export default class ShowWhen extends PureComponent { - public render() { + public override render() { const { children, when } = this.props; return when ? children : null; } diff --git a/packages/kerosene-ui/src/index.ts b/packages/kerosene-ui/src/index.ts index a168f04..1ec4bc5 100644 --- a/packages/kerosene-ui/src/index.ts +++ b/packages/kerosene-ui/src/index.ts @@ -31,7 +31,7 @@ export { default as useRect } from "./hooks/useRect"; export { default as useStableIdentity } from "./hooks/useStableIdentity"; export { default as useUpdatingRef } from "./hooks/useUpdatingRef"; -export { default as ShowWhen } from "./ShowWhen"; +export { default as ShowWhen } from "./components/ShowWhen"; export * from "./types"; diff --git a/packages/kerosene-ui/src/react-query.ts b/packages/kerosene-ui/src/react-query.ts new file mode 100644 index 0000000..01e4fbc --- /dev/null +++ b/packages/kerosene-ui/src/react-query.ts @@ -0,0 +1,14 @@ +export * from "./components/Boundaries/helpers"; + +export { + default as QueriesBoundary, + type QueriesBoundaryProps, +} from "./components/Boundaries/QueriesBoundary"; +export { + default as QueryBoundary, + type QueryBoundaryProps, +} from "./components/Boundaries/QueryBoundary"; +export { + default as SuspenseBoundary, + type SuspenseBoundaryProps, +} from "./components/Boundaries/SuspenseBoundary"; diff --git a/packages/kerosene/.npmignore b/packages/kerosene/.npmignore index 99f644a..ac74439 100644 --- a/packages/kerosene/.npmignore +++ b/packages/kerosene/.npmignore @@ -1,3 +1,7 @@ __snapshots__ +config +babel.config.js +rollup-config.ts +rollup.config.js **.spec.ts **.spec.tsx diff --git a/packages/kerosene/config/tsconfig.json b/packages/kerosene/config/tsconfig.json index e3dd0f1..8f5efb1 100644 --- a/packages/kerosene/config/tsconfig.json +++ b/packages/kerosene/config/tsconfig.json @@ -5,7 +5,7 @@ "noEmit": false, "declaration": true, "emitDeclarationOnly": true, - "declarationDir": "../lib/" + "declarationDir": "../dist/" }, "include": ["../src/**/*.ts", "../../../typings/**/*.d.ts"], "exclude": ["../src/**/*.spec.ts"] diff --git a/packages/kerosene/package.json b/packages/kerosene/package.json index 7ca04fb..038646b 100644 --- a/packages/kerosene/package.json +++ b/packages/kerosene/package.json @@ -1,6 +1,6 @@ { "name": "@kablamo/kerosene", - "version": "0.0.29", + "version": "0.0.35", "repository": "https://github.com/KablamoOSS/kerosene/tree/master/packages/kerosene", "bugs": { "url": "https://github.com/KablamoOSS/kerosene/issues" @@ -8,9 +8,18 @@ "homepage": "https://github.com/KablamoOSS/kerosene", "private": false, "license": "MIT", - "main": "lib/index.js", - "module": "es/index.js", + "main": "dist/index.cjs", + "module": "dist/index.mjs", "sideEffects": false, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + }, + "./package.json": "./package.json" + }, + "types": "dist/index.d.ts", "directories": { "doc": "readme.md" }, @@ -42,6 +51,6 @@ "build": "npm-run-all -p build:rollup build:typings", "build:rollup": "rollup -c", "build:typings": "tsc -p ./config/tsconfig.json", - "clean": "rimraf es lib" + "clean": "rimraf dist" } } diff --git a/packages/kerosene/readme.md b/packages/kerosene/readme.md index 0cf998b..beb3f0b 100644 --- a/packages/kerosene/readme.md +++ b/packages/kerosene/readme.md @@ -160,9 +160,9 @@ Returns the provided `value` rounded to the number of significant `figures` prov ## Promise -### `Deferred` +### `Deferred` -Deferred is a class that allows a Promise to be created in advance of the code that will `resolve`/`reject` it. +Deferred is a class that allows a Promise to be created in advance of the code that will `resolve`/`reject` it. Deprecated in favour of `Promise.withResolvers()`. ### `mapSeries(items, iteratee)` @@ -300,7 +300,7 @@ Like `Pick`, but distributes across all members of a union. ### `InferrableTupleOf` -Utility type which allows a generic constraint to be inferred as a tuple of T instead of an array of T +Utility type which allows a generic constraint to be inferred as a tuple of `T` instead of an array of `T`. Deprecated in favour of `const` type parameters. ### `IsUnknownOrAny` diff --git a/packages/kerosene/rollup-config.ts b/packages/kerosene/rollup-config.ts index fc6909f..cacff64 100644 --- a/packages/kerosene/rollup-config.ts +++ b/packages/kerosene/rollup-config.ts @@ -1,11 +1,6 @@ import babel from "@rollup/plugin-babel"; import path from "path"; -import type { - ExternalOption, - ModuleFormat, - OutputOptions, - RollupOptions, -} from "rollup"; +import type { ExternalOption, RollupOptions } from "rollup"; import resolve from "rollup-plugin-node-resolve"; // eslint-disable-next-line import/no-relative-packages import generateBabelConfig from "../../config/generateBabelConfig"; @@ -13,13 +8,7 @@ import packageJson from "./package.json"; const input = path.join(__dirname, "src", "index.ts"); -const output = (file: string, format: ModuleFormat): OutputOptions => ({ - dir: path.dirname(file), - format, - indent: false, - preserveModules: true, - sourcemap: true, -}); +const outputDir = path.dirname(packageJson.main); const externals = [ packageJson.dependencies, @@ -52,10 +41,22 @@ export default [ { input, output: [ - output(packageJson.main, "commonjs"), - output(packageJson.module, "esm"), + { + entryFileNames: "[name].cjs", + dir: outputDir, + format: "commonjs", + preserveModules: true, + sourcemap: true, + }, + { + entryFileNames: "[name].mjs", + dir: outputDir, + format: "esm", + preserveModules: true, + sourcemap: true, + }, ], external, plugins, }, -] as RollupOptions[]; +] satisfies RollupOptions[]; diff --git a/packages/kerosene/src/error/ExtendableError.ts b/packages/kerosene/src/error/ExtendableError.ts index 702d7e1..16e5dc4 100644 --- a/packages/kerosene/src/error/ExtendableError.ts +++ b/packages/kerosene/src/error/ExtendableError.ts @@ -11,8 +11,8 @@ * @see https://github.com/Microsoft/TypeScript/issues/13965 */ export default class ExtendableError extends Error { - constructor(message?: string) { - super(message); + constructor(...args: ConstructorParameters) { + super(...args); // NOTE: This is required for some environments like Jest where ES6 classes are downleveled to ES5 syntax. This // breaks the the `instanceof` operator for `class MyError extends Error {}` diff --git a/packages/kerosene/src/promise/Deferred.ts b/packages/kerosene/src/promise/Deferred.ts index 64cc29a..9440804 100644 --- a/packages/kerosene/src/promise/Deferred.ts +++ b/packages/kerosene/src/promise/Deferred.ts @@ -2,11 +2,14 @@ import type { Mutable } from "../types"; /** * Deferred is a class that allows a Promise to be created in advance of the code that will `resolve`/`reject` it. + * + * @deprecated Use `const { promise, resolve, reject } Promise.withResolvers()` with a polyfill if necessary instead + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers */ export default class Deferred { public readonly promise: Promise; - public readonly resolve!: (value: T) => void; + public readonly resolve!: (value: T | PromiseLike) => void; public readonly reject!: (reason?: unknown) => void; diff --git a/packages/kerosene/src/types/index.ts b/packages/kerosene/src/types/index.ts index 1942606..69464a8 100644 --- a/packages/kerosene/src/types/index.ts +++ b/packages/kerosene/src/types/index.ts @@ -161,6 +161,8 @@ export type DistributivePick> = T extends any /** * Utility type which allows a generic constraint to be inferred as a tuple of T instead of an array of T * + * @deprecated Use const type parameters instead + * * @example * ```ts * type Box = { prop: T }; @@ -169,7 +171,11 @@ export type DistributivePick> = T extends any * // $ExpectType readonly Box[] * const badBoxes = badBoxesIdentity([{ prop: "a" }, { prop: 1 }]); * - * function boxesIdentity>(boxes: TBoxes): TBoxes; + * function boxesIdentity>>(boxes: TBoxes): TBoxes; + * // $ExpectType readonly [Box, Box] + * const boxes = boxesIdentity([{ prop: "a" }, { prop: 1 }]); + * + * function boxesIdentity[]>(boxes: TBoxes): TBoxes; * // $ExpectType readonly [Box, Box] * const boxes = boxesIdentity([{ prop: "a" }, { prop: 1 }]); * ``` diff --git a/testSetup.ts b/testSetup.ts index 3577d54..ade0796 100644 --- a/testSetup.ts +++ b/testSetup.ts @@ -30,17 +30,25 @@ const CSS = class CSS { } satisfies Partial; Object.assign(globalThis, { CSS }); +// NOTE: Including this to prevent noisy console logs from ErrorBoundary components. Even though the ErrorBoundary +// catches the errors, React still logs these to the console unless, `e.defaultPrevented` is `true` +window.addEventListener("error", (e) => { + e.preventDefault(); +}); + Object.assign(window, { - matchMedia: ((query) => - ({ - get matches(): boolean { - throw new Error("Not Implemented"); - }, - media: query, - addEventListener: noop, - removeEventListener: noop, - onchange: null, - }) as Partial as MediaQueryList) as Window["matchMedia"], + matchMedia: ((query) => ({ + get matches(): boolean { + throw new Error("Not Implemented"); + }, + media: query, + dispatchEvent: () => true, + addEventListener: noop, + removeEventListener: noop, + addListener: noop, + removeListener: noop, + onchange: null, + })) satisfies Window["matchMedia"], requestAnimationFrame: (callback: FrameRequestCallback) => setTimeout(() => callback(Date.now()), 17), cancelAnimationFrame: (handle: number) => clearTimeout(handle), diff --git a/tsconfig.json b/tsconfig.json index d2e85d0..00efbb8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,25 +1,28 @@ { "compilerOptions": { "pretty": true, - "target": "ES2015", + "target": "ESNext", "lib": ["DOM", "ESNext"], "module": "ESNext", + "moduleResolution": "Bundler", + "resolvePackageJsonExports": true, + "resolvePackageJsonImports": true, "allowJs": true, "resolveJsonModule": true, "noEmit": true, "verbatimModuleSyntax": true, "strict": true, - "moduleResolution": "node", + "noImplicitOverride": true, "noUncheckedIndexedAccess": true, - "allowSyntheticDefaultImports": true, "esModuleInterop": true, "jsx": "react" }, - "include": ["**/*"], + "include": ["**/*", "**/.*", "**/.*/**/*", "**/.*/**/.*"], "exclude": [ - "**/packages/**/dist", - "**/packages/**/es", - "**/packages/**/lib", + "**/.dist", + "**/dist", + "./packages/*/es", + "./packages/*/lib", "**/node_modules" ] } diff --git a/yarn.lock b/yarn.lock index 0be3f58..f4dd9fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,10 +35,18 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.5.tgz#b1f6c86a02d85d2dd3368a2b67c09add8cd0c255" - integrity sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA== +"@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.20.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/compat-data@^7.22.6": version "7.22.6" @@ -126,6 +134,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" + integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== + dependencies: + "@babel/types" "^7.23.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/generator@^7.7.2": version "7.21.3" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" @@ -151,12 +169,12 @@ "@babel/types" "^7.22.15" "@babel/helper-compilation-targets@^7.20.7": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz#fc7319fc54c5e2fa14b2909cf3c5fd3046813e02" - integrity sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw== + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== dependencies: - "@babel/compat-data" "^7.22.5" - "@babel/helper-validator-option" "^7.22.5" + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" @@ -227,7 +245,7 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.20": +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== @@ -282,18 +300,18 @@ "@babel/types" "^7.22.5" "@babel/helper-module-transforms@^7.21.2": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" - integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== + version "7.21.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" + integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== dependencies: - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-module-imports" "^7.22.5" - "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.2" + "@babel/types" "^7.21.2" "@babel/helper-module-transforms@^7.23.3": version "7.23.3" @@ -336,7 +354,7 @@ "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" -"@babel/helper-simple-access@^7.22.5": +"@babel/helper-simple-access@^7.20.2", "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== @@ -350,14 +368,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz#88cf11050edb95ed08d596f7a044462189127a08" - integrity sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": +"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== @@ -369,6 +380,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + "@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" @@ -379,6 +395,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-option@^7.18.6": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + "@babel/helper-validator-option@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" @@ -434,6 +455,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3", "@babel/parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.5.tgz#721fd042f3ce1896238cf1b341c77eb7dee7dbea" @@ -449,6 +479,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9" integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw== +"@babel/parser@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" + integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" @@ -1263,14 +1298,14 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/template@^7.20.7", "@babel/template@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" - integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== +"@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== dependencies: - "@babel/code-frame" "^7.22.5" - "@babel/parser" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@babel/template@^7.22.15": version "7.22.15" @@ -1281,14 +1316,30 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/template@^7.3.3": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== +"@babel/template@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@^7.21.2": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" + integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.5" + "@babel/types" "^7.23.5" + debug "^4.1.0" + globals "^11.1.0" "@babel/traverse@^7.21.3", "@babel/traverse@^7.22.5": version "7.23.2" @@ -1322,13 +1373,22 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.22.5", "@babel/types@^7.3.0", "@babel/types@^7.4.4": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" - integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" + integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.5" + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@babel/types@^7.21.2", "@babel/types@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" + integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" "@babel/types@^7.22.15", "@babel/types@^7.23.0": @@ -1349,13 +1409,13 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.3.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" - integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== +"@babel/types@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -2169,6 +2229,18 @@ dependencies: defer-to-connect "^1.0.1" +"@tanstack/query-core@5.12.1": + version "5.12.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.12.1.tgz#2e04b8ab7a04b2f2cfe77c2b0c9b982477373e06" + integrity sha512-WbZztNmKq0t6QjdNmHzezbi/uifYo9j6e2GLJkodsYaYUlzMbAp91RDyeHkIZrm7EfO4wa6Sm5sxJZm5SPlh6w== + +"@tanstack/react-query@^5.12.2": + version "5.12.2" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.12.2.tgz#42b39a1ccc2bdf44137921c2395902ed4239d7eb" + integrity sha512-BeWZu8zVFH20oRc+S/K9ADPgWjEzP/XQCGBNz5IbApUwPQAdwkQYbXODVL5AyAlWiSxhx+P2xlARPBApj2Yrog== + dependencies: + "@tanstack/query-core" "5.12.1" + "@testing-library/dom@^9.0.0": version "9.3.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.1.tgz#8094f560e9389fb973fe957af41bf766937a9ee9" @@ -2395,9 +2467,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "20.3.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" - integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== + version "18.15.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.5.tgz#3af577099a99c61479149b716183e70b5239324a" + integrity sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew== "@types/node@^12.7.1": version "12.20.55" @@ -2628,12 +2700,7 @@ acorn-walk@^8.0.2: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.1.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59" - integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ== - -acorn@^8.8.1: +acorn@^8.1.0, acorn@^8.8.1: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -6330,11 +6397,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" -nwsapi@^2.2.2, nwsapi@^2.2.4: +nwsapi@^2.2.2: version "2.2.5" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.5.tgz#a52744c61b3889dd44b0a158687add39b8d935e2" integrity sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ== +nwsapi@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5" + integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6741,9 +6813,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.7.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + version "2.8.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.5.tgz#3dd8ae1ebddc4f6aa419c9b64d8c8319a7e0d982" + integrity sha512-3gzuxrHbKUePRBB4ZeU08VNkUcqEHaUaouNt0m7LGP4Hti/NuB07C7PPTM/LkWqXoJYJn2McEo5+kxPNrtQkLQ== prettier@^3.0.3: version "3.1.0" @@ -6855,6 +6927,13 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-error-boundary@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c" + integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw== + dependencies: + "@babel/runtime" "^7.12.5" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -7570,9 +7649,9 @@ string.prototype.trimstart@^1.0.7: ansi-regex "^5.0.1" strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== dependencies: ansi-regex "^6.0.1"