diff --git a/package.json b/package.json index c4fd8702..2d0e415f 100644 --- a/package.json +++ b/package.json @@ -5,14 +5,12 @@ "publish:core": "cd ./packages/core && pnpm npm publish && cd ../..", "build:react": "cd ./packages/react-async-states && pnpm build && cd ../..", "publish:react": "cd ./packages/react-async-states && pnpm npm publish && cd ../..", - "build:utils": "cd ./packages/react-async-states-utils && pnpm build && cd ../..", - "publish:utils": "cd ./packages/react-async-states-utils && pnpm npm publish && cd ../..", "build:devtools": "cd ./packages/devtools-extension && pnpm build && cd ../..", "publish:devtools": "cd ./packages/devtools-extension && pnpm npm publish && cd ../..", - "build:all": "pnpm build:core && pnpm build:react && pnpm build:utils && pnpm build:devtools", + "build:all": "pnpm build:core && pnpm build:react && pnpm build:devtools", "build": "pnpm build:all", "test": "cd ./packages/core && pnpm test && cd ../..&& cd ./packages/react-async-states && pnpm test", "prepare:all": "pnpm test && pnpm build", - "go:publish": "pnpm publish:core && pnpm publish:react && pnpm publish:utils && pnpm publish:devtools " + "go:publish": "pnpm publish:core && pnpm publish:react && pnpm publish:devtools " } } diff --git a/packages/docs/docs/utils/1.state-boundary.md b/packages/docs/docs/utils/1.state-boundary.md deleted file mode 100644 index 1186e2f7..00000000 --- a/packages/docs/docs/utils/1.state-boundary.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: State Boundary ---- -# State Boundary - -## What is this ? - -State boundary is a component that allows performing all render strategies in react: - -1. Render Then Fetch: Render the initial component, then fetch its data -2. Fetch As You Render: fetch data while rendering and suspend when pending and -throw when error. -3. Fetch Then Render: renders nothing until it fetches data, suppressing -the pending state. - -## `StateBoundaryProps` - -This component accepts the following props: - -| Property | Type | Default Value | Description | -|----------------|-----------------------------------|-------------------|-----------------------------------------------------------------------------------------------------| -| `config` | `MixedConfig` | `undefined` | The same supported configuration as the `useAsyncState` hook | -| `dependencies` | `any[]` | `[]` | The dependencies that will be passed to `useAsyncState` hook | -| `strategy` | `RenderStrategy` | `RenderThenFetch` | The applied strategy | -| `render` | `Record` | `undefined` | A record containing the component to render for each status | -| `children` | `React.ReactNode` | `null` | children are considered as a fallback whenever the `render` property doesn't have the actual status | - - -Where the `RenderStrategy` enum is defined as follows: -```ts - -export enum RenderStrategy { -FetchAsYouRender = 0, -FetchThenRender = 1, -RenderThenFetch = 2, -} - -``` - -## `useCurrentState()` - -This hook returns the current state in the boundary - -## `useBoundary(sourceKey?: string)` - -When Multiple `StateBoundary` are nested, this hook allows you to take any -state up in the tree by its `key`. - -:::warning -If the `sourceKey` isn't found, this hook will throw an `Error`. -::: - -This hook then calls `useSource` on the result obtained from the `config` given -to that state boundary. diff --git a/packages/docs/docs/utils/2.addBooleanStatus.md b/packages/docs/docs/utils/2.addBooleanStatus.md deleted file mode 100644 index 37fd5878..00000000 --- a/packages/docs/docs/utils/2.addBooleanStatus.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 1 -sidebar_label: addBooleanStatus ---- - -# `addBooleanStatus` - -This selector can be passed to `useAsyncState`, and will add the following -properties to the existing state: - -isInitial, isPending, isError, isAborted, isSuccess. - -These properties are intuitive and well typed along with the equivalent status: - -```typescript - type User = { username: string, password: string }; - - function producer(props: ProducerProps): Promise { - if (!props.args[0]) throw new Error("username or password is incorrect"); - return Promise.resolve({username: 'admin', password: 'admin'}); - } - - let {state, runc} = useAsyncState({producer, selector: defaultSelector}); - - if (state.isPending) { - let {data} = state; // type of data: null - } - if (state.isError) { - let {data} = state; // type of data: Error - } - if (state.isAborted) { - let {data} = state; // type of data: "Timeout" - } - - if (state.status === Status.initial) { - let data = state.data; // ts type of data <- User | undefined - let {isError, isSuccess} = state; - if (isSuccess) { // <- type of isSuccess is false - console.log("impossible") - } - if (isError) { // <- type of isError is false - console.log('impossible') - } - } - if (state.status === Status.pending) { - let data = state.data; // ts type of data <- null - } - if (state.status === Status.success) { - let data = state.data; // ts type of data <- User - } - if (state.status === Status.error) { - let data = state.data; // ts type of data <- Error - } - if (state.status === Status.aborted) { - let data = state.data; // ts type of data <- "Timeout" - } -``` diff --git a/packages/docs/docs/utils/_category_.json b/packages/docs/docs/utils/_category_.json deleted file mode 100644 index edb0b70c..00000000 --- a/packages/docs/docs/utils/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "position": 98, - "label": "Library utils" -} diff --git a/packages/docs/docs/utils/intro.md b/packages/docs/docs/utils/intro.md deleted file mode 100644 index d742242a..00000000 --- a/packages/docs/docs/utils/intro.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -sidebar_position: -100 -sidebar_label: Intro ---- -# React async states utils - -> Utilities on top of `react-async-states` - - -## Installation - -The utils library is available as a package on NPM for use with a module -bundler or in a Node application: - -```bash title="NPM" -npm install async-states react-async-states react-async-states-utils -``` - -```bash title="YARN" -yarn add async-states react-async-states react-async-states-utils -``` - -```bash title="PNPM" -pnpm add async-states react-async-states react-async-states-utils -``` diff --git a/packages/react-async-states-utils/.babelrc b/packages/react-async-states-utils/.babelrc deleted file mode 100644 index 23df99a2..00000000 --- a/packages/react-async-states-utils/.babelrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { - "modules": false - }], - ["@babel/preset-react", { - "useBuiltIns": true - }], - "@babel/preset-typescript" - ], - "plugins": ["@babel/plugin-proposal-class-properties"] -} diff --git a/packages/react-async-states-utils/.eslintrc b/packages/react-async-states-utils/.eslintrc deleted file mode 100644 index 2a8e38dd..00000000 --- a/packages/react-async-states-utils/.eslintrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "parser": "babel-eslint", - "extends": [ - "standard-react" - ], - "env": { - "es6": true - }, - "plugins": [ - "react" - ], - "parserOptions": { - "sourceType": "module" - }, - "rules": { - // don't force es6 functions to include space before paren - "space-before-function-paren": 0, - - // allow specifying true explicitly for boolean props - "react/jsx-boolean-value": 0 - } -} diff --git a/packages/react-async-states-utils/.travis.yml b/packages/react-async-states-utils/.travis.yml deleted file mode 100644 index bac56694..00000000 --- a/packages/react-async-states-utils/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - 8 -env: - - SKIP_PREFLIGHT_CHECK=true diff --git a/packages/react-async-states-utils/README.MD b/packages/react-async-states-utils/README.MD deleted file mode 100644 index c12cfbed..00000000 --- a/packages/react-async-states-utils/README.MD +++ /dev/null @@ -1,111 +0,0 @@ -> Utilities on top of `react-async-states` - -# React async states utils - -Utilities for react-async-states. - -[Docs link](https://incepter.github.io/react-async-states/docs/utils/intro) - -## State Boundary - -### What is this ? - -[Docs link](https://incepter.github.io/react-async-states/docs/utils/state-boundary) - -State boundary is a component that allows performing all render strategies in react: - -1. Render Then Fetch: Render the initial component, then fetch its data -2. Fetch As You Render: fetch data while rendering and suspend when pending and - throw when error. -3. Fetch Then Render: renders nothing until it fetches data, suppressing - the pending state. - -## `StateBoundaryProps` - -This component accepts the following props: - -| Property | Type | Default Value | Description | -|----------------|-----------------------------------|-------------------|-----------------------------------------------------------------------------------------------------| -| `config` | `MixedConfig` | `undefined` | The same supported configuration as the `useAsyncState` hook | -| `dependencies` | `any[]` | `[]` | The dependencies that will be passed to `useAsyncState` hook | -| `strategy` | `RenderStrategy` | `RenderThenFetch` | The applied strategy | -| `render` | `Record` | `undefined` | A record containing the component to render for each status | -| `children` | `React.ReactNode` | `null` | children are considered as a fallback whenever the `render` property doesn't have the actual status | - - -Where the `RenderStrategy` enum is defined as follows: -```ts - -export enum RenderStrategy { -FetchAsYouRender = 0, -FetchThenRender = 1, -RenderThenFetch = 2, -} - -``` - -### `useCurrentState()` - -This hook returns the current state in the boundary - -### `useBoundary(sourceKey?: string)` - -When Multiple `StateBoundary` are nested, this hook allows you to take any -state up in the tree by its `key`. - -This hook then calls `useSource` on the result obtained from the `config` given -to that state boundary. - - -## `addBooleanStatus` - -This selector can be passed to `useAsyncState`, and will add the following -properties to the existing state: - -isInitial, isPending, isError, isAborted, isSuccess. - -These properties are intuitive and well typed along with the equivalent status: - -```typescript - type User = { username: string, password: string }; - - function producer(props: ProducerProps): Promise { - if (!props.args[0]) throw new Error("username or password is incorrect"); - return Promise.resolve({username: 'admin', password: 'admin'}); - } - - let {state, runc} = useAsyncState({producer, selector: defaultSelector}); - - if (state.isPending) { - let {data} = state; // type of data: null - } - if (state.isError) { - let {data} = state; // type of data: Error - } - if (state.isAborted) { - let {data} = state; // type of data: "Timeout" - } - - if (state.status === Status.initial) { - let data = state.data; // ts type of data <- User | undefined - let {isError, isSuccess} = state; - if (isSuccess) { // <- type of isSuccess is false - console.log("impossible") - } - if (isError) { // <- type of isError is false - console.log('impossible') - } - } - if (state.status === Status.pending) { - let data = state.data; // ts type of data <- null - } - if (state.status === Status.success) { - let data = state.data; // ts type of data <- User - } - if (state.status === Status.error) { - let data = state.data; // ts type of data <- Error - } - if (state.status === Status.aborted) { - let data = state.data; // ts type of data <- "Timeout" - } -``` diff --git a/packages/react-async-states-utils/package.json b/packages/react-async-states-utils/package.json deleted file mode 100644 index 5596a23c..00000000 --- a/packages/react-async-states-utils/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "private": false, - "license": "MIT", - "version": "2.0.0-alpha-6", - "author": "incepter", - "sideEffects": false, - "main": "dist/umd/index", - "types": "dist/es/index", - "module": "dist/es/index", - "name": "react-async-states-utils", - "description": "utilities for react-async-states package", - "repository": "https://github.com/incepter/react-async-states", - "scripts": { - "test": "cross-env CI=1 jest test", - "test:cov": "cross-env CI=1 jest test --coverage", - "clean:dist": "rimraf dist", - "start": "pnpm dev", - "prebuild": "pnpm test && rimraf dist", - "build": "pnpm clean:dist && rollup -c rollup/rollup.config.js", - "dev": "pnpm clean:dist && rollup -c rollup/rollup.config.dev.js -w" - }, - "peerDependencies": { - "async-states": "^1.0.0", - "react-async-states": "^1.0.0", - "react": "^16.8.0 || ^17.0.2 || ^18.0.0" - }, - "devDependencies": { - "async-states": "workspace:^2.0.0-alpha-6", - "react-async-states": "workspace:^2.0.0-alpha-6", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@jest/globals": "^29.3.1", - "@rollup/plugin-babel": "^6.0.2", - "@rollup/plugin-commonjs": "^23.0.2", - "@rollup/plugin-json": "^5.0.1", - "@rollup/plugin-node-resolve": "^15.0.1", - "@rollup/plugin-replace": "^5.0.1", - "@rollup/plugin-terser": "^0.1.0", - "@testing-library/dom": "^8.19.0", - "@testing-library/react": "13.4.0", - "@testing-library/react-hooks": "^8.0.1", - "@types/jest": "^29.2.4", - "@types/node": "^18.11.9", - "@types/react": "18.0.25", - "babel-jest": "^29.3.1", - "circular-dependency-plugin": "^5.2.2", - "cross-env": "^7.0.3", - "eslint-config-standard": "17.0.0", - "eslint-config-standard-react": "12.0.0", - "eslint-plugin-import": "^2.13.0", - "eslint-plugin-react": "^7.10.0", - "jest": "^29.3.1", - "jest-environment-jsdom": "^29.3.1", - "react-test-renderer": "18.2.0", - "rimraf": "^3.0.2", - "rollup": "^3.3.0", - "rollup-plugin-copy": "^3.4.0", - "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-dts": "^5.0.0", - "rollup-plugin-gzip": "3.1.0", - "rollup-plugin-sourcemaps": "^0.6.3", - "rollup-plugin-typescript2": "^0.34.1", - "ts-jest": "^29.0.3", - "tslib": "^2.4.1", - "ttypescript": "^1.5.15" - }, - "engines": { - "npm": ">=5", - "node": ">=8" - }, - "keywords": [ - "react", - "state", - "async", - "promise", - "generator", - "management", - "async-state", - "react-async-state", - "react-async-states" - ], - "files": [ - "dist/*", - "README.MD", - "package.json" - ] -} diff --git a/packages/react-async-states-utils/rollup/rollup.config.dev.js b/packages/react-async-states-utils/rollup/rollup.config.dev.js deleted file mode 100644 index aa264af4..00000000 --- a/packages/react-async-states-utils/rollup/rollup.config.dev.js +++ /dev/null @@ -1,76 +0,0 @@ -const resolve = require('@rollup/plugin-node-resolve'); -const commonjs = require('@rollup/plugin-commonjs'); -const typescript = require('rollup-plugin-typescript2'); -const json = require('@rollup/plugin-json'); -const dts = require('rollup-plugin-dts').default; -const {babel} = require('@rollup/plugin-babel'); - -const devBuild = { - input: `src/index.ts`, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - 'react-async-states': 'ReactAsyncStates', - }, - output: [ - { - format: 'es', - dir: "dist/es", - sourcemap: true, - preserveModules: true, - // file: `dist/index.js`, - name: "ReactAsyncStatesUtils", - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - 'react-async-states': 'ReactAsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'react-async-states', 'async-states'], - watch: { - include: 'src/**', - }, - plugins: [ - // babel({babelHelpers: 'bundled'}), - json(), - resolve(), - commonjs(), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - // dts(), - ], -}; - -const declarationsBuild = { - input: `src/index.ts`, - output: [ - { - dir: "dist/es", - sourcemap: false, - preserveModules: true, - name: "ReactAsyncStatesUtils", - }, - ], - watch: { - include: 'src/**', - }, - plugins: [dts()], -}; - -module.exports = [ - devBuild, - declarationsBuild, -]; diff --git a/packages/react-async-states-utils/rollup/rollup.config.js b/packages/react-async-states-utils/rollup/rollup.config.js deleted file mode 100644 index c0f03214..00000000 --- a/packages/react-async-states-utils/rollup/rollup.config.js +++ /dev/null @@ -1,182 +0,0 @@ -const resolve = require('@rollup/plugin-node-resolve'); -const commonjs = require('@rollup/plugin-commonjs'); -const typescript = require('rollup-plugin-typescript2'); -const json = require('@rollup/plugin-json'); -const replace = require('@rollup/plugin-replace'); -const {babel} = require('@rollup/plugin-babel'); -const gzipPlugin = require('rollup-plugin-gzip'); -const terser = require('@rollup/plugin-terser'); -const copy = require('rollup-plugin-copy'); -const dts = require('rollup-plugin-dts').default; - -const libraryName = 'react-async-states-utils'; - -const esModulesBuild = [ - { - input: `src/index.ts`, - output: { - format: "esm", - dir: 'dist/es', - sourcemap: true, - preserveModules: true, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - 'react-async-states': 'ReactAsyncStates', - } - }, - external: ['react', 'react/jsx-runtime', 'react-async-states', 'async-states'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - json(), - resolve(), - typescript({ - tsconfigOverride: { - compilerOptions: { - target: 'ESNEXT', - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - // commonjs(), - ] - } -]; - -const umdBuild = [ - { - input: `src/index.ts`, - output: [ - { - format: "umd", - sourcemap: true, - name: "ReactAsyncStatesUtils", - file: `dist/umd/${libraryName}.development.js`, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - 'react-async-states': 'ReactAsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'react-async-states', 'async-states'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - json(), - resolve(), - babel({ - babelHelpers: "bundled", - }), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - commonjs(), - ] - }, - { - input: `src/index.ts`, - output: [ - { - format: "umd", - sourcemap: false, - name: "ReactAsyncStatesUtils", - file: `dist/umd/${libraryName}.production.js`, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - 'react-async-states': 'ReactAsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'react-async-states', 'async-states'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - replace({ - preventAssignment: true, - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - json(), - resolve(), - babel({babelHelpers: 'bundled'}), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - commonjs(), - gzipPlugin.default(), - terser({ - compress: { - reduce_funcs: false, - } - }), - copy({ - targets: [ - { - dest: 'dist/umd', - rename: 'index.js', - src: 'src/index-prod.js', - }, - ] - }), - copy({ - hook: 'closeBundle', - targets: [ - { - dest: 'dist', - src: `../README.MD`, - }, - ] - }), - ] - } -]; - -const declarationsBuild = { - input: `src/index.ts`, - output: [ - { - dir: "dist/es", - sourcemap: false, - preserveModules: true, - name: "ReactAsyncStatesUtils", - }, - ], - plugins: [dts()], -}; - -module.exports = [ - ...esModulesBuild, - ...umdBuild, - declarationsBuild, -]; diff --git a/packages/react-async-states-utils/sonar-project.properties b/packages/react-async-states-utils/sonar-project.properties deleted file mode 100644 index 4f9a86d0..00000000 --- a/packages/react-async-states-utils/sonar-project.properties +++ /dev/null @@ -1,3 +0,0 @@ -sonar.sources=src -sonar.exclusions=src/__tests__/**/*,**webpack** -sonar.javascript.lcov.reportPaths=./coverage/lcov.info diff --git a/packages/react-async-states-utils/src/StateBoundary.tsx b/packages/react-async-states-utils/src/StateBoundary.tsx deleted file mode 100644 index 4a1d0b5e..00000000 --- a/packages/react-async-states-utils/src/StateBoundary.tsx +++ /dev/null @@ -1,277 +0,0 @@ -import * as React from "react"; -import { - MixedConfig, - Source, - State, - Status, - useAsync, - useAsyncState, - UseAsyncState, - UseAsyncStateConfiguration, -} from "react-async-states"; - -let emptyArray = []; -function isFunction(fn) { - return typeof fn === "function"; -} - -export type StateBoundaryProps = { - children: React.ReactNode; - config: MixedConfig; - - dependencies?: any[]; - strategy?: RenderStrategy; - - render?: StateBoundaryRenderProp; -}; - -export type StateBoundaryRenderProp = Record; - -export type BoundaryContextValue< - T, - A extends unknown[] = unknown[], - E = unknown, - S = State -> = BoundaryContext | null; - -export type BoundarySourceContextType = { - source: Source; - parent: BoundarySourceContextType | null; -}; - -export type BoundaryContext< - T, - A extends unknown[] = unknown[], - E = unknown, - S = State -> = UseAsyncState; - -const StateBoundaryContext = - React.createContext>(null); - -export function StateBoundary( - props: StateBoundaryProps -) { - return React.createElement( - StateBoundaryImpl, - Object.assign({ key: props.strategy }, props), - props.children - ); -} - -export enum RenderStrategy { - FetchAsYouRender = 0, - FetchThenRender = 1, - RenderThenFetch = 2, -} - -let BoundarySourceContext = - React.createContext(null); - -function BoundarySource({ - source, - children, -}: { - source: Source; - children: any; -}) { - let parentSource = React.useContext(BoundarySourceContext); - let contextValue = React.useMemo( - () => ({ - source, - parent: parentSource, - }), - [source, parentSource] - ); - - return ( - - {children} - - ); -} - -function StateBoundaryImpl( - props: StateBoundaryProps -) { - if (props.strategy === RenderStrategy.FetchThenRender) { - return React.createElement(FetchThenRenderBoundary, props); - } - if (props.strategy === RenderStrategy.FetchAsYouRender) { - return React.createElement(FetchAsYouRenderBoundary, props); - } - return React.createElement(RenderThenFetchBoundary, props); -} - -function inferBoundaryChildren>( - result: UseAsyncState, - props: StateBoundaryProps -) { - if (!props.render || !result.source) { - return props.children; - } - - const { status } = result.source.getState(); - - return props.render[status] ? props.render[status] : props.children; -} - -function renderChildren(children, props) { - return isFunction(children) ? React.createElement(children, props) : children; -} - -export function RenderThenFetchBoundary( - props: StateBoundaryProps -) { - let result = useAsyncState(props.config, props.dependencies); - - const children = inferBoundaryChildren(result, props); - - let Context = StateBoundaryContext as React.Context< - BoundaryContextValue - >; - return ( - - - {renderChildren(children, result)} - - - ); -} - -export function FetchAsYouRenderBoundary( - props: StateBoundaryProps -) { - let result = useAsyncState(props.config, props.dependencies); - - result.read(); // throws - const children = inferBoundaryChildren(result, props); - - let Context = StateBoundaryContext as React.Context< - BoundaryContextValue - >; - return ( - - - {renderChildren(children, result)} - - - ); -} - -function FetchThenRenderInitialBoundary({ - dependencies = emptyArray, - result, - config, -}: { - dependencies?: any[]; - result: UseAsyncState; - config: MixedConfig; -}) { - result.source?.patchConfig({ - skipPendingStatus: true, - }); - - React.useEffect(() => { - if ( - (config as UseAsyncStateConfiguration).condition !== false - ) { - const autoRunArgs = (config as UseAsyncStateConfiguration) - .autoRunArgs; - - if (Array.isArray(autoRunArgs)) { - return result.source.run.apply(null, autoRunArgs); - } - - return result.source.run.apply(null); - } - }, dependencies); - - return null; -} - -export function FetchThenRenderBoundary< - T, - E = unknown, - R = unknown, - A extends unknown[] = unknown[], - S = State ->(props: StateBoundaryProps) { - let result = useAsyncState(props.config, props.dependencies); - - let Context = StateBoundaryContext as React.Context< - BoundaryContextValue - >; - switch (result.source?.getState().status) { - case Status.pending: - case Status.initial: { - return ( - - ); - } - case Status.error: - case Status.success: { - const children = inferBoundaryChildren(result, props); - return ( - - - {renderChildren(children, result)} - - - ); - } - } - return null; -} - -export function useCurrentState< - T, - A extends unknown[], - E, - S = State ->(): UseAsyncState { - const ctxValue = React.useContext( - StateBoundaryContext - ) as BoundaryContextValue; - - if (ctxValue === null) { - throw new Error("useCurrentState used outside StateBoundary"); - } - - return ctxValue; -} - -function recursivelyTraverseContextAndGetSource( - ctxValue: BoundarySourceContextType, - stateKey: string | undefined -) { - if (!stateKey) { - return ctxValue.source; - } - let currentSource = ctxValue.source; - if (currentSource.key === stateKey) { - return currentSource; - } - if (ctxValue.parent !== null) { - return recursivelyTraverseContextAndGetSource(ctxValue.parent, stateKey); - } - throw new Error(`(${stateKey}) was not found in boundary tree`); -} - -export function useBoundary( - stateKey?: string -): UseAsyncState { - const ctxValue = React.useContext(BoundarySourceContext); - - if (ctxValue === null) { - throw new Error("useBoundary used outside StateBoundary"); - } - - let source = recursivelyTraverseContextAndGetSource(ctxValue, stateKey); - - return useAsync(source); -} diff --git a/packages/react-async-states-utils/src/__tests__/.gitkeep b/packages/react-async-states-utils/src/__tests__/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/react-async-states-utils/src/__tests__/StateBoundary/index.test.tsx b/packages/react-async-states-utils/src/__tests__/StateBoundary/index.test.tsx deleted file mode 100644 index ed961c74..00000000 --- a/packages/react-async-states-utils/src/__tests__/StateBoundary/index.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import * as React from "react"; -import {act, render, screen} from "@testing-library/react"; -import { - RenderStrategy, - StateBoundary, - useCurrentState -} from "../../StateBoundary"; -import { - Status, - createSource -} from "react-async-states"; -import {flushPromises} from "../utils/test-utils"; - -describe('StateBoundary', () => { - it('should render in RenderThenFetch strategy', async () => { - // given - const source = createSource('test', async () => Promise.resolve(5)); - - // when - - function Component() { - const {devFlags, state} = useCurrentState(); - return ( -
- {JSON.stringify(devFlags)} - {state.status} -
- ); - } - - render( - - - - - - ) - - // then - expect(screen.getByTestId("current-mode").innerHTML) - .toEqual("[\"CONFIG_SOURCE\",\"SOURCE\",\"AUTO_RUN\"]"); - expect(screen.getByTestId("current-status").innerHTML) - .toEqual(Status.pending); - - await act(async () => { - await flushPromises(); - }); - - expect(screen.getByTestId("current-status").innerHTML) - .toEqual(Status.success); - }); - it('should render in FetchThenRender strategy', async () => { - // given - const source = createSource('test', async () => Promise.resolve(5)); - - // when - - function Component() { - const {devFlags, state} = useCurrentState(); - return ( -
- {JSON.stringify(devFlags)} - {state.status} -
- ); - } - - render( - -
- - - -
-
- ) - - // then - expect(screen.getByTestId("parent").innerHTML) - .toEqual(""); - - await act(async () => { - await flushPromises(); - }); - - expect(screen.getByTestId("current-mode")?.innerHTML) - .toEqual("[\"CONFIG_SOURCE\",\"SOURCE\"]"); - expect(screen.getByTestId("current-status").innerHTML) - .toEqual(Status.success); - }); - it('should render in FetchAsYouRender strategy', async () => { - // given - const source = createSource('test', async () => Promise.resolve(5)); - - // when - - function Component() { - const {devFlags, state} = useCurrentState(); - return ( -
- {JSON.stringify(devFlags)} - {state.status} -
- ); - } - - render( - - Loading}> - - - - - - ) - - // then - expect(screen.getByTestId("suspense-fallback").innerHTML) - .toEqual("Loading"); - - await act(async () => { - await flushPromises(); - }); - - expect(screen.getByTestId("current-status").innerHTML) - .toEqual(Status.success); - }); -}); diff --git a/packages/react-async-states-utils/src/__tests__/utils/setup.ts b/packages/react-async-states-utils/src/__tests__/utils/setup.ts deleted file mode 100644 index 8e89d3be..00000000 --- a/packages/react-async-states-utils/src/__tests__/utils/setup.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function mockDateNow() { - beforeEach(() => { - dateNowSpy = jest.spyOn(Date, 'now').mockImplementation(() => TESTS_TS); - }); - - afterEach(() => { - dateNowSpy.mockRestore(); - }); -} -export const TESTS_TS = 1487076708000; -export let dateNowSpy; diff --git a/packages/react-async-states-utils/src/__tests__/utils/test-types.ts b/packages/react-async-states-utils/src/__tests__/utils/test-types.ts deleted file mode 100644 index a3fef8d9..00000000 --- a/packages/react-async-states-utils/src/__tests__/utils/test-types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type TestUserType = { - avatar: string, - username: string, - lastName: string, - firstName: string, - permissions: TestUserPermissionType, -} - -export type TestUserPermissionType = { - -} - -export type TestLoginType = { - username: string, - password: string, -} diff --git a/packages/react-async-states-utils/src/__tests__/utils/test-utils.ts b/packages/react-async-states-utils/src/__tests__/utils/test-utils.ts deleted file mode 100644 index eaf85c19..00000000 --- a/packages/react-async-states-utils/src/__tests__/utils/test-utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function timeoutToUse(delay, resolveValue: T, setId): Promise { - return new Promise(resolve => { - const id = setTimeout(() => { - resolve(resolveValue); - }, delay); - setId(id); - }) -} - -export function rejectionTimeoutToUse(delay, resolveValue, setId) { - return new Promise(resolve => { - const id = setTimeout(() => resolve(resolveValue), delay); - setId(id); - }) -} - -export function flushPromises() { - return new Promise(jest.requireActual("timers").setImmediate) -} diff --git a/packages/react-async-states-utils/src/index-prod.js b/packages/react-async-states-utils/src/index-prod.js deleted file mode 100644 index 283fd9e6..00000000 --- a/packages/react-async-states-utils/src/index-prod.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./react-async-states-utils.production.js'); -} else { - module.exports = require('./react-async-states-utils.development.js'); -} diff --git a/packages/react-async-states-utils/src/index.ts b/packages/react-async-states-utils/src/index.ts deleted file mode 100644 index e2482369..00000000 --- a/packages/react-async-states-utils/src/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -export { - useBoundary, - StateBoundary, - RenderStrategy, - useCurrentState, - FetchThenRenderBoundary, - RenderThenFetchBoundary, - FetchAsYouRenderBoundary -} from "./StateBoundary"; - -export type { - BoundarySourceContextType, - BoundaryContextValue, - BoundaryContext, - StateBoundaryProps, - StateBoundaryRenderProp -} from "./StateBoundary"; - -export {run, runLane, runInContext, runLaneInContext} from "./run"; -export {addBooleanStatus} from "./selectors"; -export type {StateWithBooleanStatus} from "./selectors"; - diff --git a/packages/react-async-states-utils/src/run.ts b/packages/react-async-states-utils/src/run.ts deleted file mode 100644 index e04dd2c7..00000000 --- a/packages/react-async-states-utils/src/run.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - AsyncStateKeyOrSource, - Source, - requestContext, - StateInterface, - isSource, -} from "async-states"; - -export function run( - keyOrSource: AsyncStateKeyOrSource, - ...args: A -) { - return runImpl(keyOrSource, null, undefined, ...args); -} - -export function runLane( - keyOrSource: AsyncStateKeyOrSource, - lane: string | undefined, - ...args: A -) { - return runImpl(keyOrSource, null, lane, ...args); -} -export function runInContext( - keyOrSource: AsyncStateKeyOrSource, - context: any, - ...args: A -) { - return runImpl(keyOrSource, context, undefined, ...args); -} - -export function runLaneInContext( - keyOrSource: AsyncStateKeyOrSource, - context: any, - lane: string | undefined, - ...args: A -) { - return runImpl(keyOrSource, context, lane, ...args); -} - -function runImpl( - keyOrSource: AsyncStateKeyOrSource, - context: any, - lane: string | undefined, - ...args: A -) { - if (isSource(keyOrSource)) { - return (keyOrSource as Source).getLane(lane).run(...args); - } - if (typeof keyOrSource === "string") { - let instance = requestContext(context).get(keyOrSource) as StateInterface< - T, - A, - E - >; - if (instance) { - return instance.actions.run.apply(null, args); - } - } - return undefined; -} diff --git a/packages/react-async-states-utils/src/selectors.ts b/packages/react-async-states-utils/src/selectors.ts deleted file mode 100644 index ccc0c65f..00000000 --- a/packages/react-async-states-utils/src/selectors.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { - InitialState, - PendingState, - SuccessState, - ErrorState, - State, - Status, -} from "react-async-states"; - -type InitialExtendedState = { - isInitial: true; - isPending: false; - isError: false; - isAborted: false; - isSuccess: false; -}; -type PendingExtendedState = { - isInitial: false; - isPending: true; - isError: false; - isAborted: false; - isSuccess: false; -}; -type SuccessExtendedState = { - isInitial: false; - isPending: false; - isError: false; - isAborted: false; - isSuccess: true; -}; -type ErrorExtendedState = { - isInitial: false; - isPending: false; - isError: true; - isAborted: false; - isSuccess: false; -}; -type AbortedExtendedState = { - isInitial: false; - isPending: false; - isError: false; - isAborted: true; - isSuccess: false; -}; - -export type StateWithBooleanStatus< - T, - A extends unknown[] = unknown[], - E = unknown, -> = - | (InitialState & InitialExtendedState) - | (PendingState & PendingExtendedState) - | (SuccessState & SuccessExtendedState) - | (ErrorState & ErrorExtendedState); - -type ExtendStatusReturn = - | InitialExtendedState - | PendingExtendedState - | SuccessExtendedState - | AbortedExtendedState - | ErrorExtendedState; - -function extendStatus< - T, - E = unknown, - R = unknown, - A extends unknown[] = unknown[] ->(state: State): ExtendStatusReturn { - let status = state.status; - switch (status) { - case Status.initial: { - return { - isInitial: true, - isPending: false, - isError: false, - isAborted: false, - isSuccess: false, - }; - } - case Status.pending: { - return { - isInitial: false, - isPending: true, - isError: false, - isAborted: false, - isSuccess: false, - }; - } - case Status.success: { - return { - isInitial: false, - isPending: false, - isError: false, - isAborted: false, - isSuccess: true, - }; - } - case Status.error: { - return { - isInitial: false, - isPending: false, - isError: true, - isAborted: false, - isSuccess: false, - }; - } - } - throw new Error(`Status ${status} isn't recognized!`); -} - -export function addBooleanStatus< - T, - E = unknown, - R = unknown, - A extends unknown[] = unknown[] ->(state: State): StateWithBooleanStatus { - let extended = extendStatus(state); - return Object.assign({}, extended, state) as StateWithBooleanStatus; -} diff --git a/packages/react-async-states-utils/src/utils.ts b/packages/react-async-states-utils/src/utils.ts deleted file mode 100644 index 77c5ad53..00000000 --- a/packages/react-async-states-utils/src/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ - -export function isFunction(fn): fn is Function { - return typeof fn === "function"; -} -export function noop(): void { - // that's a noop fn -} diff --git a/packages/react-async-states-utils/tsconfig.json b/packages/react-async-states-utils/tsconfig.json deleted file mode 100644 index 5abf2da2..00000000 --- a/packages/react-async-states-utils/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "strict": false, - "baseUrl": ".", - "types": ["node", "jest", "react"], - }, - "include": ["src"], - "exclude": [ - "node_modules", - "src/index-prod.js", - "src/useQuery.ts" - ] -} diff --git a/packages/react-async-states-utils/tsconfig.test.json b/packages/react-async-states-utils/tsconfig.test.json deleted file mode 100644 index 8efdd8ab..00000000 --- a/packages/react-async-states-utils/tsconfig.test.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "types": ["node", "jest", "react"], - - }, - "include": ["src"], - "exclude": [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] -} diff --git a/packages/state-fiber/.eslintrc b/packages/state-fiber/.eslintrc deleted file mode 100644 index 810c8ace..00000000 --- a/packages/state-fiber/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "parser": "babel-eslint", - "env": { - "es6": true - }, - "parserOptions": { - "sourceType": "module" - }, - "rules": { - // don't force es6 functions to include space before paren - "space-before-function-paren": 0 - } -} diff --git a/packages/state-fiber/README.MD b/packages/state-fiber/README.MD deleted file mode 100644 index 4de3b902..00000000 --- a/packages/state-fiber/README.MD +++ /dev/null @@ -1,55 +0,0 @@ -# state fiber - -## Usage example - -```tsx -const { data, error, isPending } = useFiber({ - args: [userId], - key: "user-details", - producer: getUserDetails, -}); -``` - -```tsx -// source has everything: run, setData, setError, setState... -const [data, source] = useData({ - args: [userId], - key: "user-details", - producer: getUserDetails, -}); -``` - -```tsx -const { data, source } = useAsync({ - args: [userId], - key: "user-details", - producer: getUserDetails, -}); -``` - -## Contribution - -### Lib explanations and tradeoffs - -The library stores the state in a data structure called StateFiber, you can -refer to the `IStateFiber` interface to see the attributes. - -The most important properties are: - -- `root: StateRoot`: contains the state name, configuration and function -- `version: number`: this is incremented each time the state changes -- `actions: FiberActions`: actions to manipulate the state -- `task: RunTask`: the latest finished task -- `pending: RunTask`: the currently pending run -- `state: State`: the current state (`initial`, `success` or `error`) - -A task represents a run, means it is created the moment you call `run`, -it references anything related to than run and to be able to replicate it: - -- `args`: the run args -- `payload`: a shallow clone of the StateFiber's payload when ran -- `indicators`: contains whether the run has been aborted etc.. -- `callbacks`: onSuccess and onError callbacks to be executed when setting state -- `controller`: the AbortController linked to that run -- `result`: the immediate result after calling `run`, may be `T` or `Promise` -- `promise`: A promise object that the library and react understand diff --git a/packages/state-fiber/index.js b/packages/state-fiber/index.js deleted file mode 100644 index 53dbc28d..00000000 --- a/packages/state-fiber/index.js +++ /dev/null @@ -1 +0,0 @@ -console.log("something interesting is cooking. reserving name for now"); diff --git a/packages/state-fiber/jest.config.js b/packages/state-fiber/jest.config.js deleted file mode 100644 index eba84a97..00000000 --- a/packages/state-fiber/jest.config.js +++ /dev/null @@ -1,24 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'jsdom', - moduleDirectories: ['node_modules', 'src', '/..'], - transform: { - '^.+\\.ts$': ['ts-jest', {tsConfig: '/tsconfig.test.json'}], - '^.+\\.js$': 'babel-jest', - '^.+\\.mjs$': 'babel-jest', - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - coveragePathIgnorePatterns: [ - "__tests__", - "index-prod.js", - "configuration-warn", - "devtools", - "type*.ts" - ], - testMatch: [ - "**/*.test.js", - "**/*.test.ts", - "**/*.test.tsx" - ], -}; diff --git a/packages/state-fiber/package.json b/packages/state-fiber/package.json deleted file mode 100644 index 707ff09b..00000000 --- a/packages/state-fiber/package.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "private": false, - "license": "MIT", - "author": "incepter", - "sideEffects": false, - "version": "0.0.0", - "main": "dist/umd/index", - "types": "dist/es/index", - "module": "dist/es/index", - "name": "state-fiber", - "description": "state-fiber", - "scripts": { - "test": "cross-env CI=1 jest test", - "test:cov": "cross-env CI=1 jest test --coverage", - "clean:dist": "rimraf dist", - "start": "pnpm dev", - "prebuild": "pnpm test && rimraf dist", - "build": "pnpm clean:dist && rollup -c rollup/rollup.config.js", - "dev": "pnpm clean:dist && rollup -c rollup/rollup.config.dev.js -w" - }, - "peerDependencies": { - "react": "^18.3.0" - }, - "devDependencies": { - "react": "canary", - "react-dom": "canary", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.22.2", - "@babel/preset-react": "^7.22.3", - "@babel/preset-typescript": "^7.21.5", - "@jest/globals": "^29.5.0", - "@rollup/plugin-babel": "^6.0.3", - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.2", - "@rollup/plugin-replace": "^5.0.2", - "@rollup/plugin-terser": "^0.4.3", - "@testing-library/dom": "^9.3.0", - "@testing-library/react": "14.0.0", - "@testing-library/react-hooks": "^8.0.1", - "@types/jest": "^29.5.1", - "@types/node": "^20.2.5", - "@types/react": "18.2.7", - "babel-jest": "^29.5.0", - "circular-dependency-plugin": "^5.2.2", - "cross-env": "^7.0.3", - "eslint-config-standard": "17.0.0", - "eslint-config-standard-react": "13.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-react": "^7.32.2", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "prettier": "^2.8.8", - "react-test-renderer": "18.2.0", - "rimraf": "^5.0.1", - "rollup": "^3.23.0", - "rollup-plugin-copy": "^3.4.0", - "rollup-plugin-delete": "^2.0.0", - "rollup-plugin-dts": "^5.3.0", - "rollup-plugin-gzip": "3.1.0", - "rollup-plugin-sourcemaps": "^0.6.3", - "rollup-plugin-typescript2": "^0.34.1", - "ts-jest": "^29.1.0", - "tslib": "^2.5.2", - "ttypescript": "^1.5.15", - "typescript": "5.2.2" - }, - "engines": { - "npm": ">=5", - "node": ">=8" - }, - "keywords": [ - "transition", - "suspense", - "concurrent mode", - "hooks", - "react-use", - "react", - "state", - "async", - "promise" - ], - "files": [ - "dist/*", - "README.MD", - "package.json" - ] -} diff --git a/packages/state-fiber/rollup/rollup.config.dev.js b/packages/state-fiber/rollup/rollup.config.dev.js deleted file mode 100644 index 0be64c5c..00000000 --- a/packages/state-fiber/rollup/rollup.config.dev.js +++ /dev/null @@ -1,91 +0,0 @@ -const resolve = require('@rollup/plugin-node-resolve'); -const commonjs = require('@rollup/plugin-commonjs'); -const typescript = require('rollup-plugin-typescript2'); -const json = require('@rollup/plugin-json'); -const dts = require('rollup-plugin-dts').default; -const {babel} = require('@rollup/plugin-babel'); - -const devBuild = { - input: `src/index.ts`, - globals: { - react: 'React', - }, - output: [ - { - format: 'es', - dir: "dist/es", - sourcemap: true, - preserveModules: true, - // file: `dist/index.js`, - name: "ReactPromiseCache", - globals: { - react: 'React', - } - }, - ], - external: ['react'], - watch: { - include: 'src/**', - }, - plugins: [ - babel({babelHelpers: 'bundled'}), - json(), - resolve(), - commonjs(), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - ], -}; - -const declarationsBuild = { - input: `src/index.ts`, - output: [ - { - format: 'es', - dir: "dist/es", - sourcemap: false, - preserveModules: true, - name: "ReactPromiseCache", - globals: { - react: 'React', - } - }, - ], - external: ['react'], - watch: { - include: 'src/**', - }, - plugins: [ - json(), - typescript({ - tsconfigOverride: { - compilerOptions: { - sourceMap: false, - declaration: false, - declarationMap: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - dts(), - ], -}; - -module.exports = [ - devBuild, - declarationsBuild, -]; diff --git a/packages/state-fiber/rollup/rollup.config.js b/packages/state-fiber/rollup/rollup.config.js deleted file mode 100644 index 4ce5ed15..00000000 --- a/packages/state-fiber/rollup/rollup.config.js +++ /dev/null @@ -1,202 +0,0 @@ -const resolve = require('@rollup/plugin-node-resolve'); -const commonjs = require('@rollup/plugin-commonjs'); -const typescript = require('rollup-plugin-typescript2'); -const json = require('@rollup/plugin-json'); -const replace = require('@rollup/plugin-replace'); -const {babel} = require('@rollup/plugin-babel'); -const gzipPlugin = require('rollup-plugin-gzip'); -const terser = require('@rollup/plugin-terser'); -const copy = require('rollup-plugin-copy'); -const dts = require('rollup-plugin-dts').default; - -const libraryName = 'state-fiber'; - -const esModulesBuild = [ - { - input: `src/index.ts`, - output: { - format: "esm", - dir: 'dist/es', - sourcemap: true, - preserveModules: true, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - } - }, - external: ['react'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - json(), - resolve(), - typescript({ - tsconfigOverride: { - compilerOptions: { - target: 'ESNEXT', - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - commonjs(), - ] - } -]; - -const umdBuild = [ - { - input: `src/index.ts`, - output: [ - { - format: "umd", - sourcemap: true, - name: "ReactAsyncStates", - file: `dist/umd/${libraryName}.development.js`, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'async-states'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - json(), - resolve(), - babel({ - babelHelpers: "bundled", - }), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - commonjs(), - ] - }, - { - input: `src/index.ts`, - output: [ - { - format: "umd", - sourcemap: false, - name: "ReactAsyncStates", - file: `dist/umd/${libraryName}.production.js`, - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'async-states'], - treeshake: { - moduleSideEffects: false, - }, - plugins: [ - replace({ - preventAssignment: true, - 'process.env.NODE_ENV': JSON.stringify('production'), - }), - json(), - resolve(), - babel({babelHelpers: 'bundled'}), - typescript({ - tsconfigOverride: { - compilerOptions: { - declaration: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - commonjs(), - gzipPlugin.default(), - terser({ - compress: { - reduce_funcs: false, - } - }), - copy({ - targets: [ - { - dest: 'dist/umd', - rename: 'index.js', - src: 'src/index-prod.js', - }, - ] - }), - copy({ - hook: 'closeBundle', - targets: [ - { - dest: 'dist', - src: `../../README.MD`, - }, - ] - }), - ] - } -]; - -const declarationsBuild = { - input: `src/index.ts`, - output: [ - { - format: 'es', - dir: "dist/es", - sourcemap: false, - preserveModules: true, - name: "ReactAsyncStates", - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - 'async-states': 'AsyncStates', - } - }, - ], - external: ['react', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'async-states'], - plugins: [ - json(), - typescript({ - tsconfigOverride: { - compilerOptions: { - sourceMap: false, - declaration: false, - declarationMap: false, - }, - exclude: [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] - } - }), - dts(), - ], -}; - -module.exports = [ - ...esModulesBuild, - ...umdBuild, - declarationsBuild, -]; diff --git a/packages/state-fiber/src/__tests__/basic.test.tsx b/packages/state-fiber/src/__tests__/basic.test.tsx deleted file mode 100644 index 0a76c2e5..00000000 --- a/packages/state-fiber/src/__tests__/basic.test.tsx +++ /dev/null @@ -1,336 +0,0 @@ -import * as React from "react"; -import { act, fireEvent, render, screen } from "@testing-library/react"; -import { useAsync, useFiber } from "../react/FiberHooks"; -import { expect } from "@jest/globals"; -import { waitFor } from "@testing-library/dom"; -import { doesNodeExist, flushPromises } from "./utils"; -import { FnProps } from "../core/_types"; - -describe("should perform basic forms separately", () => { - async function getCurrentNumberProducer( - props: FnProps - ) { - await new Promise((res) => setTimeout(res, 100)); - return props.args[0]; - } - it("Should basic useAsync", async () => { - jest.useFakeTimers(); - let renderCount = 0; - let userIds = [1, 2, 3]; - - function Test() { - let [id, setId] = React.useState(1); - return ( -
- Pending...} - > - - - -
- ); - } - function Buttons({ setId }) { - return ( -
- {userIds.map((u) => ( - - ))} -
- ); - } - function SetUserButton({ id, setUserId }) { - let [isPending, start] = React.useTransition(); - return ( - - ); - } - - function Component1({ userId }) { - renderCount += 1; - let result = useAsync({ - lazy: false, - args: [userId], - initialValue: 0, - key: "test-basic-useAsync", - producer: getCurrentNumberProducer, - }); - - return {result.data}; - } - - // when - - render( - - - - ); - // initially, data doesn't exist, useAsync would suspend - expect(renderCount).toBe(1); // did suspend - expect(doesNodeExist("success_1")).toBe(false); - expect(screen.getByTestId("suspense-1").innerHTML).toEqual("Pending..."); - - renderCount = 0; - await jest.advanceTimersByTime(99); - // verify same state - - expect(renderCount).toBe(0); - expect(doesNodeExist("success_1")).toBe(false); - expect(screen.getByTestId("suspense-1").innerHTML).toEqual("Pending..."); - - await jest.advanceTimersByTime(1); - // now that the promise did resolve, react won't be recovering right away - // it will take a few time to recover, this delay is arbitrary and we - // should not rely on it either ways, so, wait for it - await waitFor(() => screen.getByTestId("success_1")); - expect(renderCount).toBe(2); - expect(doesNodeExist("success_1")).toBe(true); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("success_1").innerHTML).toEqual("1"); - - // load user 2 - - let user2 = screen.getByTestId("button_2"); - let user3 = screen.getByTestId("button_3"); - - renderCount = 0; - fireEvent.click(user2); - - await act(async () => { - await flushPromises(); - }); - - // now, react will render under a 'Transition', and since it won't paint - // this means that the app rendered because top level userId changed - // which means that the tree is rendered under a transition with previous - // props and state. - expect(renderCount).toBe(1); - expect(doesNodeExist("success_1")).toBe(true); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(user2.getAttribute("class")).toBe("pending"); - expect(screen.getByTestId("success_1").innerHTML).toEqual("1"); - - // react will then recover in setTimeout, so we advance by 0 and then try - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(user3.getAttribute("class")).toBe(""); - expect(user2.getAttribute("class")).toBe("pending"); - expect(screen.getByTestId("success_1").innerHTML).toEqual("1"); - - expect(renderCount).toBe(1); - - // now we will interrupt the pending state for user 2, and go to user 3 - - // load user 3 - renderCount = 0; - fireEvent.click(user3); - - // now, react will render under a 'Transition', so it won't render the - // userId 3 yet, because it suspended in component 1 - expect(renderCount).toBe(1); - expect(user2.getAttribute("class")).toBe("pending"); - expect(user3.getAttribute("class")).toBe("pending"); - expect(doesNodeExist("success_1")).toBe(true); - - expect(renderCount).toBe(1); - - // react will then recover in setTimeout, so we advance by 0 and then try - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(renderCount).toBe(1); - expect(doesNodeExist("success_1")).toBe(true); - expect(user2.getAttribute("class")).toBe("pending"); - expect(user3.getAttribute("class")).toBe("pending"); - - // keep going and resolve pending - renderCount = 0; - await act(async () => { - await jest.advanceTimersByTime(100); - }); - - // now that the promise did resolve, react won't be recovering right away - // it will take a few time to recover, this delay is arbitrary and we - // should not rely on it either ways, so, wait for it - await waitFor(() => screen.getByTestId("success_1")); - await act(async () => { - await jest.advanceTimersByTime(1000); - }); - expect(user2.getAttribute("class")).toBe(""); - - expect(renderCount).toBe(2); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(doesNodeExist("success_1")).toBe(true); - expect(screen.getByTestId("success_1").innerHTML).toEqual("3"); - }); - it("Should basic useFiber", async () => { - jest.useFakeTimers(); - let renderCount = 0; - let userIds = [1, 2, 3]; - - function Test() { - let [id, setId] = React.useState(1); - return ( -
- Pending...} - > - - - -
- ); - } - function Buttons({ setId }) { - return ( -
- {userIds.map((u) => ( - - ))} -
- ); - } - function SetUserButton({ id, setUserId }) { - let [isPending, start] = React.useTransition(); - return ( - - ); - } - - function Component2({ userId }) { - renderCount += 1; - let result = useFiber({ - lazy: false, - args: [userId], - key: "test-basic-useFiber", - producer: getCurrentNumberProducer, - }); - - return ( - <> - {result.isPending && ( - <> - {userId} - - {result.state.props.args[0]} - - - )} - {result.isSuccess && ( - {result.data} - )} - - ); - } - // when - - render( - - - - ); - expect(renderCount).toBe(4); // (initial + pending) x 2 (strict mode) - expect(doesNodeExist("success_2")).toBe(false); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("1"); - - renderCount = 0; - await jest.advanceTimersByTime(99); - - // verify same state - expect(renderCount).toBe(0); - expect(doesNodeExist("success_2")).toBe(false); - - await act(async () => { - await jest.advanceTimersByTime(1); - await flushPromises(); - }); - - expect(renderCount).toBe(2); - expect(doesNodeExist("success_2")).toBe(true); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("success_2").innerHTML).toEqual("1"); - - // load user 2 - let user2 = screen.getByTestId("button_2"); - let user3 = screen.getByTestId("button_3"); - - renderCount = 0; - fireEvent.click(user2); - - await act(async () => { - await flushPromises(); - }); - - expect(renderCount).toBe(4); // two renders because userId changed, and 2 because we passed from success to pending - expect(user2.getAttribute("class")).toBe(""); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("2"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("2"); - - renderCount = 0; - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(user3.getAttribute("class")).toBe(""); - expect(user2.getAttribute("class")).toBe(""); - expect(doesNodeExist("success_2")).toBe(false); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("2"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("2"); - - expect(renderCount).toBe(0); // same as before - - // load user 3 - renderCount = 0; - fireEvent.click(user3); - - expect(renderCount).toBe(4); - expect(user2.getAttribute("class")).toBe(""); - expect(user3.getAttribute("class")).toBe(""); - expect(doesNodeExist("success_2")).toBe(false); - - expect(renderCount).toBe(4); // same: userId + pending x 2 (strict mode) - - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(user2.getAttribute("class")).toBe(""); - expect(user3.getAttribute("class")).toBe(""); - expect(doesNodeExist("success_2")).toBe(false); - - // keep going and resolve pending - renderCount = 0; - await act(async () => { - await jest.advanceTimersByTime(100); - }); - - expect(renderCount).toBe(2); - expect(user2.getAttribute("class")).toBe(""); - expect(doesNodeExist("success_2")).toBe(true); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(screen.getByTestId("success_2").innerHTML).toEqual("3"); - }); -}); diff --git a/packages/state-fiber/src/__tests__/shouldNotFail.test.tsx b/packages/state-fiber/src/__tests__/shouldNotFail.test.tsx deleted file mode 100644 index c63a2c54..00000000 --- a/packages/state-fiber/src/__tests__/shouldNotFail.test.tsx +++ /dev/null @@ -1,286 +0,0 @@ -import * as React from "react"; -import { act, fireEvent, render, screen } from "@testing-library/react"; -import { useAsync, useFiber } from "../react/FiberHooks"; -import { expect } from "@jest/globals"; -import { waitFor } from "@testing-library/dom"; -import { doesNodeExist, flushPromises } from "./utils"; -import { FnProps } from "../core/_types"; - -describe("should render useAsync and useFiber correctly", () => { - let initialRendersCount = { "1": 0, "2": 0, "3": 0 }; - async function getCurrentNumberProducer( - props: FnProps - ) { - await new Promise((res) => setTimeout(res, 100)); - return props.args[0]; - } - it("Should use useAsync and useFiber and sync two separate Suspense boundaries", async () => { - jest.useFakeTimers(); - let userIds = [1, 2, 3]; - let renderCount = { ...initialRendersCount }; - let resetRendersCount = () => (renderCount = { ...initialRendersCount }); - - function Test() { - let [id, setId] = React.useState(1); - return ( -
- Pending...} - > - - - - - -
- ); - } - function Buttons({ setId }) { - return ( -
- {userIds.map((u) => ( - - ))} -
- ); - } - function SetUserButton({ id, setUserId }) { - let [isPending, start] = React.useTransition(); - return ( - - ); - } - - function Component1({ userId }) { - renderCount["1"] += 1; - let result = useAsync({ - key: "test", - lazy: false, - args: [userId], - initialValue: 0, - producer: getCurrentNumberProducer, - }); - - return {result.data}; - } - - function Component2({ userId }) { - renderCount["2"] += 1; - let result = useFiber({ - key: "test", - args: [userId], - producer: getCurrentNumberProducer, - }); - - if (result.isPending) { - return ( - <> - {userId} - - {result.state.props.args[0]} - - - ); - } - return {result.data}; - } - function Component3({ userId }) { - renderCount["3"] += 1; - let result = useFiber({ - key: "test", - args: [userId], - producer: getCurrentNumberProducer, - }); - - if (result.isPending) { - return ( - <> - {userId} - - {result.state.props.args[0]} - - - ); - } - return {result.data}; - } - - // when - - render( - - - - ); - // initially, data doesn't exist, useAsync would suspend - expect(renderCount["1"]).toBe(1); // did suspend - expect(renderCount["2"]).toBe(0); // did not render at all - expect(renderCount["3"]).toBe(2); // strict mode - expect(screen.getByTestId("suspense-1").innerHTML).toEqual("Pending..."); - expect(doesNodeExist("success_1")).toBe(false); - expect(doesNodeExist("success_2")).toBe(false); - expect(doesNodeExist("pending_2_props")).toBe(false); - expect(doesNodeExist("pending_2_optimistic")).toBe(false); - expect(screen.getByTestId("pending_3_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_3_optimistic").innerHTML).toEqual("1"); - - resetRendersCount(); - await jest.advanceTimersByTime(99); - // verify same state - - expect(renderCount["1"]).toBe(0); - expect(renderCount["2"]).toBe(0); - expect(renderCount["3"]).toBe(0); - expect(screen.getByTestId("suspense-1").innerHTML).toEqual("Pending..."); - expect(doesNodeExist("success_1")).toBe(false); - expect(doesNodeExist("success_2")).toBe(false); - expect(doesNodeExist("pending_2_props")).toBe(false); - expect(doesNodeExist("pending_2_optimistic")).toBe(false); - expect(screen.getByTestId("pending_3_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_3_optimistic").innerHTML).toEqual("1"); - - resetRendersCount(); - await jest.advanceTimersByTime(1); - // now that the promise did resolve, react won't be recovering right away - // it will take a few time to recover, this delay is arbitrary and we - // should not rely on it either ways, so, wait for it - await waitFor(() => screen.getByTestId("success_1")); - expect(renderCount["1"]).toBe(2); - expect(renderCount["2"]).toBe(2); - expect(renderCount["3"]).toBe(2); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(doesNodeExist("success_1")).toBe(true); - expect(doesNodeExist("success_2")).toBe(true); - expect(doesNodeExist("pending_2_props")).toBe(false); - expect(doesNodeExist("pending_3_props")).toBe(false); - expect(doesNodeExist("pending_2_optimistic")).toBe(false); - expect(doesNodeExist("pending_3_optimistic")).toBe(false); - expect(screen.getByTestId("success_1").innerHTML).toEqual("1"); - expect(screen.getByTestId("success_2").innerHTML).toEqual("1"); - expect(screen.getByTestId("success_3").innerHTML).toEqual("1"); - - // load user 2 - - let user2 = screen.getByTestId("button_2"); - let user3 = screen.getByTestId("button_3"); - - resetRendersCount(); - fireEvent.click(user2); - - await act(async () => { - await flushPromises(); - }); - - // now, react will render under a 'Transition', and since it won't paint - // this means that the app rendered because top level userId changed - // which means that the tree is rendered under a transition with previous - // props and state. - expect(renderCount["1"]).toBe(1); - expect(renderCount["2"]).toBe(0); - expect(renderCount["3"]).toBe(2); - expect(user2.getAttribute("class")).toBe("pending"); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(doesNodeExist("success_1")).toBe(true); - expect(screen.getByTestId("success_1").innerHTML).toEqual("1"); - expect(screen.getByTestId("success_2").innerHTML).toEqual("1"); - expect(screen.getByTestId("success_3").innerHTML).toEqual("1"); - - // react will then recover in setTimeout, so we advance by 0 and then try - resetRendersCount(); - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(user3.getAttribute("class")).toBe(""); - expect(user2.getAttribute("class")).toBe("pending"); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_3_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("2"); - expect(screen.getByTestId("pending_3_optimistic").innerHTML).toEqual("2"); - // await waitFor(() => screen.getByTestId("pending_3_optimistic")); - - expect(renderCount["1"]).toBe(1); - // pending render that breaks its transition (scheduled render due to run) - expect(renderCount["2"]).toBe(2); - // renders twice: 1. render from App, 2. scheduled render due to run - // todo: optimize this and make one single render (bailout scheduled) - expect(renderCount["3"]).toBe(4); - - // now we will interrupt the pending state for user 2, and go to user 3 - - // load user 2 - resetRendersCount(); - fireEvent.click(user3); - - // now, react will render under a 'Transition', so it won't render the - // userId 3 yet, because it suspended in component 1 - expect(renderCount["1"]).toBe(1); - expect(renderCount["2"]).toBe(0); - expect(renderCount["3"]).toBe(2); - expect(user2.getAttribute("class")).toBe("pending"); - expect(user3.getAttribute("class")).toBe("pending"); - expect(doesNodeExist("success_1")).toBe(true); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_3_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("2"); - expect(screen.getByTestId("pending_3_optimistic").innerHTML).toEqual("2"); - // await waitFor(() => screen.getByTestId("pending_3_optimistic")); - - expect(renderCount["1"]).toBe(1); - expect(renderCount["2"]).toBe(0); - expect(renderCount["3"]).toBe(2); - - // react will then recover in setTimeout, so we advance by 0 and then try - resetRendersCount(); - await act(async () => { - await jest.advanceTimersByTime(0); - }); - - expect(renderCount["1"]).toBe(1); - // pending render that breaks its transition (scheduled render due to run) - expect(renderCount["2"]).toBe(2); - // renders twice: 1. render from App, 2. scheduled render due to run - // todo: optimize this and make one single render (bailout scheduled) - expect(renderCount["3"]).toBe(4); - expect(user2.getAttribute("class")).toBe("pending"); - expect(user3.getAttribute("class")).toBe("pending"); - expect(doesNodeExist("success_1")).toBe(true); - expect(screen.getByTestId("pending_2_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_3_props").innerHTML).toEqual("1"); - expect(screen.getByTestId("pending_2_optimistic").innerHTML).toEqual("3"); - expect(screen.getByTestId("pending_3_optimistic").innerHTML).toEqual("3"); - - // keep going and resolve pending - resetRendersCount(); - await act(async () => { - await jest.advanceTimersByTime(100); - }); - - // now that the promise did resolve, react won't be recovering right away - // it will take a few time to recover, this delay is arbitrary and we - // should not rely on it either ways, so, wait for it - await waitFor(() => screen.getByTestId("success_1")); - expect(user2.getAttribute("class")).toBe(""); - - expect(renderCount["1"]).toBe(2); - expect(renderCount["2"]).toBe(2); - expect(renderCount["3"]).toBe(2); - expect(doesNodeExist("suspense-1")).toBe(false); - expect(doesNodeExist("success_1")).toBe(true); - expect(doesNodeExist("success_2")).toBe(true); - expect(doesNodeExist("pending_2_props")).toBe(false); - expect(doesNodeExist("pending_3_props")).toBe(false); - expect(doesNodeExist("pending_2_optimistic")).toBe(false); - expect(doesNodeExist("pending_3_optimistic")).toBe(false); - expect(screen.getByTestId("success_1").innerHTML).toEqual("3"); - expect(screen.getByTestId("success_2").innerHTML).toEqual("3"); - expect(screen.getByTestId("success_3").innerHTML).toEqual("3"); - }); -}); diff --git a/packages/state-fiber/src/__tests__/sync.test.tsx b/packages/state-fiber/src/__tests__/sync.test.tsx deleted file mode 100644 index a32fcce4..00000000 --- a/packages/state-fiber/src/__tests__/sync.test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from "react"; -import { act, fireEvent, render, screen } from "@testing-library/react"; -import { useData } from "../react/FiberHooks"; -import { expect } from "@jest/globals"; -import { doesNodeExist } from "./utils"; - -describe("should perform basic sync form separately", () => { - it("Should basic useData", async () => { - let actions; - let renderCount = 0; - jest.useFakeTimers(); - - function Test() { - renderCount += 1; - let [data, source] = useData({ - initialValue: "", - key: "sync-basic", - }); - actions = source; - return ( - <> - {data} - source.setData(e.target.value)} - /> - - ); - } - - render( - - - - ); - - let input = screen.getByTestId("input"); - let inputResult = screen.getByTestId("input-res"); - - expect(renderCount).toBe(2); - expect(inputResult.innerHTML).toEqual(""); - - renderCount = 0; - act(() => { - fireEvent.change(input, { target: { value: "aa" } }); - }); - - expect(renderCount).toBe(2); - expect(inputResult.innerHTML).toEqual("aa"); - - renderCount = 0; - act(() => { - actions.setData("Hello"); - }); - - expect(renderCount).toBe(2); - expect(inputResult.innerHTML).toEqual("Hello"); - }); - it("Should basic error boundary", async () => { - let actions; - let renderCount = 0; - jest.useFakeTimers(); - - class ErrorBoundary extends React.Component<{ children }> { - state = { error: null }; - static getDerivedStateFromError(error) { - return { error }; - } - render() { - if (this.state.error) { - return ( - {String(this.state.error)} - ); - } - return this.props.children; - } - } - - function Test() { - renderCount += 1; - let [data, source] = useData({ - key: "error-basic", - initialValue: "data", - }); - actions = source; - return ( - <> - {data} - - ); - } - - render( - - - - - - ); - - expect(doesNodeExist("error-boundary")).toBeFalsy(); - expect(screen.getByTestId("input-res").innerHTML).toBe("data"); - - let consoleSpy = jest.fn(); - act(() => { - actions.setError(new Error("Blow the tree")); - }); - - expect(doesNodeExist("input-res")).toBeFalsy(); - expect(screen.getByTestId("error-boundary").innerHTML).toBe( - "Error: Blow the tree" - ); - }); -}); diff --git a/packages/state-fiber/src/__tests__/utils.ts b/packages/state-fiber/src/__tests__/utils.ts deleted file mode 100644 index 285a08da..00000000 --- a/packages/state-fiber/src/__tests__/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { screen } from "@testing-library/react"; - -export function flushPromises() { - return new Promise(jest.requireActual("timers").setImmediate); -} - -export function doesNodeExist(testId) { - try { - screen.getByTestId(testId); - return true; - } catch (e: any) { - return false; - } -} diff --git a/packages/state-fiber/src/core/Fiber.ts b/packages/state-fiber/src/core/Fiber.ts deleted file mode 100644 index a405d7e7..00000000 --- a/packages/state-fiber/src/core/Fiber.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - CachedStateList, - FiberDataUpdate, - FiberDataUpdater, - ILibraryContext, - InitialState, - IStateFiber, - IStateFiberActions, - IStateFiberRoot, - PendingRun, - PendingUpdate, - RuncProps, - RunTask, - State, - StateRoot, - UpdateQueue, -} from "./_types"; -import { runcStateFiber, runpStateFiber, runStateFiber } from "./FiberRun"; -import { - enqueueDataUpdate, - enqueueErrorUpdate, - enqueueStateUpdate, -} from "./FiberUpdate"; -import { requestContext } from "./FiberContext"; -import { dispatchNotification } from "./FiberDispatch"; - -let stateFiberId = 0; - -export class StateFiberRoot - implements IStateFiberRoot -{ - root: StateRoot; - constructor(root: StateRoot) { - this.root = root; - this.bind = this.bind.bind(this); - } - - bind(ctx: any): IStateFiber { - let context = requestContext(ctx); - let fiber = new StateFiber(this.root, context); - context.set(this.root.key, fiber); - return fiber; - } -} - -export function loadFiberCache( - fiber: IStateFiber -) { - let loader = fiber.root.config?.cacheConfig?.load; - if (!loader) { - return; - } - let loadedCache = loader(); - // todo: support async cache - if (loadedCache) { - fiber.cache = loadedCache; - } -} - -export class StateFiber - extends StateFiberRoot - implements IStateFiber -{ - id: number; - payload: P; - version: number; - - state: State; // TBD - task: RunTask | null; - pending: RunTask | null; - - context: ILibraryContext; - listeners: Map; - actions: IStateFiberActions; - - pendingRun: PendingRun | null; - pendingUpdate: PendingUpdate | null; - - queue: UpdateQueue | null; - queueId: ReturnType | null; - - cache: CachedStateList | null; - - constructor(root: StateRoot, context: ILibraryContext) { - super(root); - let existingFiber = context.get(root.key); - - if (existingFiber) { - return existingFiber; - } - - this.version = 0; - this.task = null; - this.pending = null; - this.context = context; - this.payload = null as P; - this.id = ++stateFiberId; - this.listeners = new Map(); - this.actions = new StateFiberActions(this); - - this.cache = null; - this.queue = null; - this.queueId = null; - this.pendingRun = null; - this.pendingUpdate = null; - - loadFiberCache(this); - this.state = computeInitialState(this); - - // context.set(root.key, this); - } -} - -function computeInitialState( - instance: IStateFiber -): InitialState { - let { root } = instance; - let config = root.config; - let state: InitialState = { - timestamp: Date.now(), - status: "initial", - }; - - if (config && Object.hasOwn(config, "initialValue")) { - let initializer = config.initialValue; - - - if (typeof initializer !== "function") { - state.data = initializer; - } else { - let initializerFn = initializer as ( - cache: CachedStateList | null - ) => T; - state.data = initializerFn(instance.cache); - } - } - - return Object.freeze(state as InitialState); -} - -export class StateFiberActions - implements IStateFiberActions -{ - private readonly self: IStateFiber; - - constructor(instance: IStateFiber) { - this.self = instance; - this.getPayload = this.getPayload.bind(this); - this.mergePayload = this.mergePayload.bind(this); - this.run = this.run.bind(this); - this.runp = this.runp.bind(this); - this.runc = this.runc.bind(this); - this.setData = this.setData.bind(this); - this.setError = this.setError.bind(this); - this.setState = this.setState.bind(this); - this.runc = this.runc.bind(this); - this.dispose = this.dispose.bind(this); - this.getState = this.getState.bind(this); - this.subscribe = this.subscribe.bind(this); - } - - getPayload(): P { - return this.self.payload; - } - - mergePayload(p: Partial

): void { - this.self.payload = Object.assign({}, this.self.payload, p); - } - - run(...args: A): () => void { - let instance = this.self; - let payload = Object.assign({}, instance.payload); - - return runStateFiber(instance, args, payload); - } - - runc(props: RuncProps): () => void { - return runcStateFiber(this.self, props); - } - - runp(...args: A): Promise { - let instance = this.self; - let payload = Object.assign({}, instance.payload); - - return runpStateFiber(instance, args, payload); - } - - setData(update: FiberDataUpdate | FiberDataUpdater): void { - enqueueDataUpdate(this.self, update, null); - dispatchNotification(this.self); - } - - setError(error: R): void { - enqueueErrorUpdate(this.self, error, null); - dispatchNotification(this.self); - } - - setState( - state: State | ((prev: State) => State) - ): void { - enqueueStateUpdate(this.self, state, null); - dispatchNotification(this.self); - } - - dispose(): void {} - - getState(): any { - return this.self.state; - } - - subscribe(cb: Function, data: any): () => void { - let instance = this.self; - instance.listeners.set(cb, data); - return () => instance.listeners.delete(cb); - } -} diff --git a/packages/state-fiber/src/core/FiberCache.ts b/packages/state-fiber/src/core/FiberCache.ts deleted file mode 100644 index 7bb5bed0..00000000 --- a/packages/state-fiber/src/core/FiberCache.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { CachedState, IStateFiber, RunTask, StateRoot } from "./_types"; - -export function hasCacheEnabled(root: StateRoot) { - return root.config?.cacheConfig?.enabled || false; -} - -function defaultHashingFunction( - args: A, - payload: P -): string { - return JSON.stringify([args, payload]); -} - -export function computeTaskHash( - root: StateRoot, - task: RunTask -) { - let cacheConfig = root.config!.cacheConfig; - let { payload, args } = task; - - let hashingFunction = cacheConfig?.hash || defaultHashingFunction; - - return hashingFunction(args, payload); -} - -export function requestCacheWithHash( - fiber: IStateFiber, - taskHash: string -): CachedState | null { - let cache = fiber.cache; - if (!cache) { - return null; - } - return cache[taskHash]; -} - -export function shouldReplaceStateWithCache( - fiber: IStateFiber, - existingCache: CachedState -): boolean { - let currentState = fiber.state; - let stateFromCache = existingCache.state; - - // this tests based on equality check because we should always keep them in - // sync. Always set the state with the cached reference. - return !Object.is(currentState, stateFromCache); -} - -export function didCachedStateExpire( - root: StateRoot, - existingCache: CachedState -) { - let expiryConfig = root.config?.cacheConfig?.deadline; - if (expiryConfig === undefined) { - // when no deadline is specified, keep the state in memory indefinitely - return false; - } - - let now = Date.now(); - let millisUntilExpiry = - typeof expiryConfig === "function" - ? expiryConfig(existingCache.state) - : expiryConfig; - - return now > millisUntilExpiry + existingCache.at; -} diff --git a/packages/state-fiber/src/core/FiberContext.ts b/packages/state-fiber/src/core/FiberContext.ts deleted file mode 100644 index b795e689..00000000 --- a/packages/state-fiber/src/core/FiberContext.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ILibraryContext, IStateFiber } from "./_types"; - -let LibraryContexts = new Map(); - -export function requestContext(ctx: any): ILibraryContext { - let existing = LibraryContexts.get(ctx); - if (existing) { - return existing; - } - - let context = new LibraryContext(ctx); - LibraryContexts.set(ctx, context); - return context; -} - -export function retainContext(ctx: any, context: ILibraryContext) { - LibraryContexts.set(ctx, context); -} - -export function removeContext(ctx: any) { - return LibraryContexts.delete(ctx); -} - -export class LibraryContext implements ILibraryContext { - private readonly ctx: any; - private readonly list: Map>; - constructor(ctx: any) { - this.ctx = ctx; - this.get = this.get.bind(this); - this.set = this.set.bind(this); - this.remove = this.remove.bind(this); - this.list = new Map>(); - } - get(key: string): IStateFiber | undefined { - return this.list.get(key); - } - set(key: string, instance: IStateFiber): void { - this.list.set(key, instance); - } - remove(key: string) { - return this.list.delete(key); - } -} diff --git a/packages/state-fiber/src/core/FiberDispatch.ts b/packages/state-fiber/src/core/FiberDispatch.ts deleted file mode 100644 index 3de9b1ff..00000000 --- a/packages/state-fiber/src/core/FiberDispatch.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - FiberPromise, - FulfilledPromise, - IStateFiber, - RejectedPromise, - RunTask, - SavedProps, -} from "./_types"; -import { cleanFiberTask } from "./FiberTask"; -import { - enqueueDataUpdate, - enqueueErrorUpdate, - enqueuePendingUpdate, -} from "./FiberUpdate"; - -export function dispatchFiberAbortEvent( - fiber: IStateFiber, - task: RunTask -) { - cleanFiberTask(task); - - // remove if it is the current pending - if (fiber.pending === task) { - fiber.pending = null; - } -} - -export function trackPendingFiberPromise( - fiber: IStateFiber, - task: RunTask -) { - let indicators = task.indicators; - // this is the pending path, the task is always pending - let promise = task.result as FiberPromise; - task.promise = promise; - - // this means this promise has never been tracked or used by the lib or react - if (!promise.status) { - let untrackedPromise = promise as FiberPromise; - untrackedPromise.status = "pending"; - untrackedPromise.then( - (value: T) => { - untrackedPromise.status = "fulfilled"; - (untrackedPromise as FulfilledPromise).value = value; - - if (!indicators.aborted) { - enqueueDataUpdate(fiber, value, task); - dispatchNotification(fiber); - } - }, - (error: R) => { - untrackedPromise.status = "rejected"; - (untrackedPromise as RejectedPromise).reason = error; - if (!indicators.aborted) { - enqueueErrorUpdate(fiber, error, task); - dispatchNotification(fiber); - } - } - ); - } - - if (!indicators.aborted && promise.status === "pending") { - dispatchFiberPendingEvent(fiber, task); - } -} - -export function dispatchNotification( - fiber: IStateFiber -) { - for (let listener of fiber.listeners.values()) { - listener.callback(); - } -} - -export function dispatchNotificationExceptFor( - fiber: IStateFiber, - cause: any -) { - for (let listener of fiber.listeners.values()) { - if (cause !== listener.update) { - listener.callback(); - } - } -} - -let notifyOnPending = true; -export function togglePendingNotification(nextValue: boolean) { - let previousValue = notifyOnPending; - notifyOnPending = nextValue; - return previousValue; -} -export function dispatchFiberPendingEvent( - fiber: IStateFiber, - task: RunTask // unused -) { - let config = fiber.root.config; - - // the first thing is to check whether this "pending" update should be - // delayed or skipped. - // If the fiber is already pending, then "skipPending" has no effect. - // if the fiber isn't pending, and "skipPending" is configured, then it should - // delay it a little. - if ( - !fiber.pending && // fiber.pending holds the pending state - // means we have skipPending configured with a positive delay - config && - config.skipPendingDelayMs && - config.skipPendingDelayMs > 0 - ) { - let now = Date.now(); - let pendingUpdateAt = now; - let delay = config.skipPendingDelayMs; - - // if a previous pending update is scheduled, we should remove the elapsed - // time from the delay - let pendingUpdate = fiber.pendingUpdate; - if (pendingUpdate) { - // this cleanup after referencing ensures that pendingUpdate.task - // gets invalidated so even if it resolves later, it doesn't affect fiber - cleanPendingFiberUpdate(fiber, task); - let prevPendingAt = pendingUpdate.at; - let elapsedTime = now - prevPendingAt; - - // we deduce here the elapsed time from the previous "scheduled pending" - delay = delay - elapsedTime; - pendingUpdateAt = delay < 0 ? now : prevPendingAt; - } - - let id = setTimeout(() => { - // do nothing when task was aborted - // and also only update if status is "still pending" - // or else, the resolving event is responsible for update and notification - if (!task.indicators.aborted) { - if (task.promise?.status === "pending") { - cleanPendingFiberUpdate(fiber, task); - enqueuePendingUpdate(fiber, task); - if (notifyOnPending) { - dispatchNotification(fiber); - } - } - } - }, delay); - fiber.pendingUpdate = { id, at: pendingUpdateAt, task }; - } else { - cleanPendingFiberUpdate(fiber, task); - enqueuePendingUpdate(fiber, task); - if (notifyOnPending) { - dispatchNotification(fiber); - } - } -} - -// pending update has a status "pending", we may delay it if we choose -// to skipPending under some delay. -// How skipPending works: when the run yields a promise and skipPending -// is configured, it is delayed by the chosen delay and not added to the -// queue immediately. When a non "pending" update arrives, the delayed -// pending has no sense any more and should be removed. -export function cleanPendingFiberUpdate( - fiber: IStateFiber, - task: RunTask | null -) { - let pendingUpdate = fiber.pendingUpdate; - if (pendingUpdate) { - if (task !== null && pendingUpdate.task !== task) { - // mark the pending task as aborted only if it is not the current one - cleanFiberTask(pendingUpdate.task); - } - - // clear the scheduled pending update - clearTimeout(pendingUpdate.id); - // remove the update from the fiber - fiber.pendingUpdate = null; - } -} - -export function savedPropsFromDataUpdate( - data, - payload: P -): SavedProps { - // sorry.. - return { - args: [data], - payload: Object.assign({}, payload), - } as SavedProps; -} diff --git a/packages/state-fiber/src/core/FiberRun.ts b/packages/state-fiber/src/core/FiberRun.ts deleted file mode 100644 index 9ea750f9..00000000 --- a/packages/state-fiber/src/core/FiberRun.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { - CachedState, - FnProps, - ICallbacks, - IStateFiber, - RuncProps, - RunTask, - StateRoot, -} from "./_types"; -import { cleanFiberTask, createTask } from "./FiberTask"; -import { - dispatchFiberAbortEvent, - dispatchNotification, - trackPendingFiberPromise, -} from "./FiberDispatch"; -import { isPromise, noop } from "../utils"; -import { - enqueueDataUpdate, - enqueueErrorUpdate, - enqueueStateUpdate, -} from "./FiberUpdate"; -import { - computeTaskHash, - didCachedStateExpire, - hasCacheEnabled, - requestCacheWithHash, - shouldReplaceStateWithCache, -} from "./FiberCache"; - -export function runStateFiber( - fiber: IStateFiber, - args: A, - payload: P -) { - return runcStateFiber(fiber, { args, payload }); -} - -export function runpStateFiber( - fiber: IStateFiber, - args: A, - payload: P -): Promise { - return new Promise((resolve) => { - runcStateFiber(fiber, { - args, - payload, - onError: resolve, - onSuccess: resolve, - }); - }); -} - -export function runcStateFiber( - fiber: IStateFiber, - props: RuncProps -): () => void { - let callbacks: ICallbacks = { - onError: props.onError, - onSuccess: props.onSuccess, - }; - - let task = createTask((props.args || []) as A, props.payload as P, callbacks); - return runFiberTask(fiber, task); -} - -function executeScheduledTaskRun( - fiber: IStateFiber, - task: RunTask -) { - if (!task.indicators.aborted) { - let pendingRun = fiber.pendingRun; - if (!pendingRun) { - return; - } - - clearTimeout(pendingRun.id); - fiber.pendingRun = null; - - executeFiberTask(fiber, task); - } -} - -function scheduleRunOnFiber( - fiber: IStateFiber, - task: RunTask, - effectDurationMs: number -) { - let id = setTimeout( - () => executeScheduledTaskRun(fiber, task), - effectDurationMs - ); - fiber.pendingRun = { id, at: Date.now() }; - - // cleanup - return () => clearTimeout(id); -} - -// returns the latest ran task, or null. -// the latest run is either fiber.pending or fiber.task -// I test on task.at since they are both technically tasks -function resolveLatestTaskFiberWasRan( - fiber: IStateFiber -) { - let { pending, task } = fiber; - if (pending) { - if (!task) { - return pending; - } - return pending.at > task.at ? pending : task; - } else if (task) { - return task; - } - return null; -} - -function throttleFiberRun( - fiber: IStateFiber, - task: RunTask, - duration: number -) { - let latestRanTask = resolveLatestTaskFiberWasRan(fiber); - - if (latestRanTask) { - let isElapsed = Date.now() - latestRanTask.at > duration; - - if (isElapsed) { - return executeFiberTask(fiber, task); - } else { - // do nothing, you are throttled, probably return previous cleanup ? - return () => {}; - } - } else { - return executeFiberTask(fiber, task); - } -} - -function bailoutFiberPendingRun(fiber: IStateFiber) { - if (!fiber.pendingRun) { - return; - } - clearTimeout(fiber.pendingRun.id); - fiber.pendingRun = null; -} - -function runFiberTask( - fiber: IStateFiber, - task: RunTask -) { - let root = fiber.root; - let applyEffects = doesRootHaveEffects(root); - if (applyEffects) { - let { effect, effectDurationMs } = root.config!; - let effectDuration = effectDurationMs!; - switch (effect) { - case "delay": - case "debounce": { - bailoutFiberPendingRun(fiber); - return scheduleRunOnFiber(fiber, task, effectDuration); - } - case "throttle": { - bailoutFiberPendingRun(fiber); - return throttleFiberRun(fiber, task, effectDuration); - } - default: { - throw new Error("Unsupported run effect " + String(effect)); - } - } - } - - return executeFiberTask(fiber, task); -} - -function executeFiberTask( - fiber: IStateFiber, - task: RunTask -) { - let { root, task: latestTask, pending: pendingTask } = fiber; - - let { fn } = root; - - if (!fn || typeof fn !== "function") { - throw new Error("Not supported yet"); - } - - if (latestTask) { - cleanFiberTask(latestTask); - } - - if (pendingTask) { - cleanFiberTask(pendingTask); - } - - if (hasCacheEnabled(fiber.root)) { - let taskHash = computeTaskHash(root, task); - let existingCache = requestCacheWithHash(fiber, taskHash); - if (existingCache) { - let isEntryExpired = didCachedStateExpire(root, existingCache); - if (!isEntryExpired) { - let replace = shouldReplaceStateWithCache(fiber, existingCache); - if (replace) { - enqueueStateUpdate(fiber, existingCache.state, task); - dispatchNotification(fiber); - } - return noop; - } - } - - task.hash = taskHash; - } - - task.at = Date.now(); - task.clean = () => { - let currentlyPending = fiber.pending; - dispatchFiberAbortEvent(fiber, task); - - // only notify when we clean/abort this particular task when it's the current - if (currentlyPending === task) { - dispatchNotification(fiber); - } - }; - - let result: T | Promise; - - try { - // todo: add other effects (emit, select, run) - result = fn({ - args: task.args, - abort: task.clean, - onAbort: task.onAbort, - payload: task.payload, - signal: task.controller.signal, - isAborted(): boolean { - return task.indicators.aborted; - }, - } as FnProps); - } catch (e: any) { - enqueueErrorUpdate(fiber, e as R, task); - dispatchNotification(fiber); - return noop; - } - - task.result = result; - if (isPromise(result)) { - trackPendingFiberPromise(fiber, task); - } else { - enqueueDataUpdate(fiber, result, task); - } - - return task.clean; -} - -export function doesRootHaveEffects(root: StateRoot) { - let config = root.config; - if (!config) { - return false; - } - let enableByEffect = !!config.effect || false; - if (enableByEffect) { - return (config.effectDurationMs || 0) > 0; - } -} diff --git a/packages/state-fiber/src/core/FiberTask.ts b/packages/state-fiber/src/core/FiberTask.ts deleted file mode 100644 index 7dfcb8e1..00000000 --- a/packages/state-fiber/src/core/FiberTask.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ICallbacks, RunTask } from "./_types"; -import { noop } from "../utils"; - -export function createTask( - args: A, - payload: P, - callbacks: ICallbacks -): RunTask { - const controller = new AbortController(); - const onAbort = (cb: () => void) => - controller.signal.addEventListener("abort", cb); - - return { - args, - payload, - callbacks, - - onAbort, - controller, - clean: noop, - result: null, - promise: null, - - hash: null, - at: Date.now(), - indicators: { - aborted: false, - cleared: false, - }, - }; -} - -export function cleanFiberTask( - task: RunTask -) { - task.controller.abort(); - task.indicators.cleared = true; - task.indicators.aborted = true; -} diff --git a/packages/state-fiber/src/core/FiberUpateHelpers.ts b/packages/state-fiber/src/core/FiberUpateHelpers.ts deleted file mode 100644 index 929a5457..00000000 --- a/packages/state-fiber/src/core/FiberUpateHelpers.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { - FiberDataUpdater, - FiberErrorUpdate, - FiberStateUpdate, - FiberStateUpdater, - IStateFiber, - RunTask, - SavedProps, - UpdateQueue, -} from "./_types"; -import { - cleanPendingFiberUpdate, - savedPropsFromDataUpdate, -} from "./FiberDispatch"; -import { cleanFiberTask } from "./FiberTask"; -import { hasCacheEnabled } from "./FiberCache"; - -export const UPDATE_KIND_SET_DATA /* */ = 0 as const; -export const UPDATE_KIND_SET_DATA_FUNC /* */ = 1 as const; -export const UPDATE_KIND_SET_ERROR /* */ = 2 as const; -export const UPDATE_KIND_SET_STATE /* */ = 3 as const; -export const UPDATE_KIND_SET_STATE_FUNC /**/ = 4 as const; -export const UPDATE_KIND_PENDING /* */ = 5 as const; - -export function createDataUpdate( - data: T, - task: RunTask | null -): UpdateQueue { - return { - task, - next: null, - value: data, - kind: UPDATE_KIND_SET_DATA, - }; -} -export function flushDataUpdate( - fiber: IStateFiber, - value: T, - task: RunTask | null -) { - cleanPendingFiberUpdate(fiber, task); - - if (fiber.pending) { - let pendingTask = fiber.pending; - fiber.pending = null; - cleanFiberTask(pendingTask); - } - - // null means setData was called directly, not when ran - let savedProps: SavedProps = - task !== null - ? { args: task.args, payload: task.payload } - : savedPropsFromDataUpdate(value, fiber.payload); - - fiber.task = task; - fiber.version += 1; - - let newState = { - data: value, - props: savedProps, - timestamp: Date.now(), - status: "success" as const, - }; - - fiber.state = newState; - - let successCallback = task?.callbacks?.onSuccess; - if (typeof successCallback === "function") { - successCallback(value); - } - - // this means only one thing: cache is enabled, and we should store this state - if (task && task.hash !== null) { - if (!fiber.cache) { - fiber.cache = {}; - } - - fiber.cache[task.hash] = { - state: newState, - at: newState.timestamp, - }; - - let cachePersistFunction = fiber.root.config?.cacheConfig?.persist; - if (cachePersistFunction) { - cachePersistFunction(fiber.cache); - } - } - - if ( - task === fiber.pending || - (fiber.pendingUpdate && task === fiber.pendingUpdate.task) - ) { - fiber.task = task; - fiber.pending = null; - } -} - -export function createDataUpdater( - data: FiberDataUpdater, - task: RunTask | null -): UpdateQueue { - return { - task, - next: null, - value: data, - kind: UPDATE_KIND_SET_DATA_FUNC, - }; -} -export function flushDataUpdater( - fiber: IStateFiber, - value: FiberDataUpdater, - task: RunTask | null -) { - let newData: T; - let updater = value; - let currentState = fiber.state; - - try { - if (currentState.status === "success") { - newData = updater(currentState.data); - } else { - let initialState = fiber.root.config?.initialValue as T; - newData = updater(initialState); - } - - return flushDataUpdate(fiber, newData, task); - } catch (e) { - return flushErrorUpdate(fiber, e as R, task); - } -} - -export function createErrorUpdate( - error: FiberErrorUpdate, - task: RunTask | null -): UpdateQueue { - return { - task, - next: null, - value: error, - kind: UPDATE_KIND_SET_ERROR, - }; -} -export function flushErrorUpdate( - fiber: IStateFiber, - value: R, - task: RunTask | null -) { - cleanPendingFiberUpdate(fiber, task); - if (fiber.pending) { - let pendingTask = fiber.pending; - fiber.pending = null; - cleanFiberTask(pendingTask); - } - - // null means setData was called directly, not when ran - let savedProps = - task !== null - ? { args: task.args, payload: task.payload } - : savedPropsFromDataUpdate(value, fiber.payload); - - fiber.task = task; - fiber.state = { - error: value, - status: "error", - props: savedProps, - timestamp: Date.now(), - }; - fiber.version += 1; - - let errorCallback = task?.callbacks?.onError; - if (typeof errorCallback === "function") { - errorCallback(value); - } - - if ( - task === fiber.pending || - (fiber.pendingUpdate && task === fiber.pendingUpdate.task) - ) { - fiber.task = task; - fiber.pending = null; - } -} - -export function createStateUpdate( - state: FiberStateUpdate, - task: RunTask | null -): UpdateQueue { - return { - task, - next: null, - value: state, - kind: UPDATE_KIND_SET_STATE, - }; -} -export function flushStateUpdate( - fiber: IStateFiber, - value: FiberStateUpdate, - task: RunTask | null -) { - cleanPendingFiberUpdate(fiber, task); - if (fiber.pending) { - let pendingTask = fiber.pending; - fiber.pending = null; - cleanFiberTask(pendingTask); - } - - fiber.task = task; - fiber.version += 1; - fiber.state = value; -} - -export function createStateUpdater( - state: FiberStateUpdater, - task: RunTask | null -): UpdateQueue { - return { - task, - next: null, - value: state, - kind: UPDATE_KIND_SET_STATE_FUNC, - }; -} -export function flushStateUpdater( - fiber: IStateFiber, - value: FiberStateUpdater, - task: RunTask | null -) { - return flushStateUpdate(fiber, value(fiber.state), task); -} - -export function createPendingUpdate( - task: RunTask -): UpdateQueue { - return { - task, - next: null, - kind: UPDATE_KIND_PENDING, - }; -} -export function flushPendingUpdate( - fiber: IStateFiber, - task: RunTask -) { - fiber.version += 1; - fiber.pending = task; -} diff --git a/packages/state-fiber/src/core/FiberUpdate.ts b/packages/state-fiber/src/core/FiberUpdate.ts deleted file mode 100644 index aa978f2d..00000000 --- a/packages/state-fiber/src/core/FiberUpdate.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - FiberDataUpdater, - IStateFiber, - RunTask, - State, - StateFiberUpdate, - UpdateQueue, -} from "./_types"; -import { - createDataUpdate, - createDataUpdater, - createErrorUpdate, - createPendingUpdate, - createStateUpdate, - createStateUpdater, - flushDataUpdate, - flushDataUpdater, - flushErrorUpdate, - flushPendingUpdate, - flushStateUpdate, - flushStateUpdater, - UPDATE_KIND_PENDING, - UPDATE_KIND_SET_DATA, - UPDATE_KIND_SET_DATA_FUNC, - UPDATE_KIND_SET_ERROR, - UPDATE_KIND_SET_STATE, - UPDATE_KIND_SET_STATE_FUNC, -} from "./FiberUpateHelpers"; -import { dispatchNotification } from "./FiberDispatch"; - -function enqueueUpdate( - fiber: IStateFiber, - updateToQueue: UpdateQueue -) { - if (!fiber.queue) { - fiber.queue = updateToQueue; - } else { - let tail: UpdateQueue = fiber.queue; - - while (tail.next !== null) { - tail = tail.next; - } - - tail.next = updateToQueue; - } -} - -export function ensureFiberQueueIsScheduled( - fiber: IStateFiber, - delay: number -) { - if (fiber.queueId) { - clearTimeout(fiber.queueId); - } - let queueId = setTimeout(() => { - if (fiber.queueId === queueId) { - attemptToFlushUpdateQueue(fiber); - dispatchNotification(fiber); - fiber.queueId = null; - } - }, delay); - - fiber.queueId = queueId; -} - -function attemptToFlushUpdateQueue( - fiber: IStateFiber -) { - // the first thing to check before flushing the queue is whether the fiber - // is pending, and we have keepPending configured. - - if (!fiber.queue) { - return; - } - - let isFiberPending = !!fiber.pending; - let hasKeepPending = (fiber.root.config?.keepPendingForMs || 0) > 0; - - if (isFiberPending && hasKeepPending) { - // when it is pending with keepPending configured - // this means that we should skip this processing and flush until the - // pending state is over (keepPending delay elapsed) - - // Moreover, we should ensure that the queue processing is scheduled - - let now = Date.now(); - let pendingStartedAt = fiber.pending!.at; - let rootConfig = fiber.root.config!; - let delay: number = rootConfig.keepPendingForMs!; - let skipPendingDelay: number = rootConfig.skipPendingDelayMs || 0; - - let realElapsedStart = pendingStartedAt + skipPendingDelay; - let elapsedTime = now - realElapsedStart; - let didKeepPendingExpire = now > realElapsedStart + delay; - - if (didKeepPendingExpire) { - imperativelyFlushQueue(fiber); - return; - } else { - ensureFiberQueueIsScheduled(fiber, delay); - } - } else { - imperativelyFlushQueue(fiber); - } -} - -function imperativelyFlushQueue( - fiber: IStateFiber -) { - let queue = fiber.queue; - if (queue) { - while (queue !== null) { - switch (queue.kind) { - case UPDATE_KIND_SET_DATA: { - flushDataUpdate(fiber, queue.value, queue.task); - break; - } - case UPDATE_KIND_SET_DATA_FUNC: { - flushDataUpdater(fiber, queue.value, queue.task); - break; - } - case UPDATE_KIND_SET_ERROR: { - flushErrorUpdate(fiber, queue.value, queue.task); - break; - } - case UPDATE_KIND_SET_STATE: { - flushStateUpdate(fiber, queue.value, queue.task); - break; - } - case UPDATE_KIND_SET_STATE_FUNC: { - flushStateUpdater(fiber, queue.value, queue.task); - break; - } - case UPDATE_KIND_PENDING: { - flushPendingUpdate(fiber, queue.task); - break; - } - } - queue = queue.next; - } - fiber.queue = null; - } -} - -// use the queue if: -// 1. there is already a queue -// 2. config.keepPending is configured and fiber is pending -function shouldEnqueueUpdate(fiber: IStateFiber) { - if (fiber.queue) { - return true; - } - - let config = fiber.root.config; - if (config && (config.keepPendingForMs || 0) > 0) { - let isFiberPending = !!fiber.pending; - // pending state is always stored in fiber.pending - return isFiberPending; - } - - return false; -} - -export function enqueueDataUpdate( - fiber: IStateFiber, - update: StateFiberUpdate, - task: RunTask | null -) { - let shouldUseQueue = shouldEnqueueUpdate(fiber); - // shortcut when queue isn't applied - if (!shouldUseQueue) { - if (typeof update === "function") { - flushDataUpdater(fiber, update as (prev: T) => T, task); - } else { - flushDataUpdate(fiber, update, task); - } - - return; - } - - let updateToQueue: UpdateQueue; - - if (typeof update === "function") { - updateToQueue = createDataUpdater(update as FiberDataUpdater, task); - } else { - updateToQueue = createDataUpdate(update, task); - } - - enqueueUpdate(fiber, updateToQueue); - attemptToFlushUpdateQueue(fiber); -} - -export function enqueueErrorUpdate( - fiber: IStateFiber, - error: R, - task: RunTask | null -) { - let shouldUseQueue = shouldEnqueueUpdate(fiber); - // shortcut when queue isn't applied - if (!shouldUseQueue) { - flushErrorUpdate(fiber, error, task); - return; - } - - let updateToQueue = createErrorUpdate(error, task); - - enqueueUpdate(fiber, updateToQueue); - attemptToFlushUpdateQueue(fiber); -} - -export function enqueueStateUpdate( - fiber: IStateFiber, - update: State | ((prev: State) => State), - task: RunTask | null -) { - let shouldUseQueue = shouldEnqueueUpdate(fiber); - // shortcut when queue isn't applied - if (!shouldUseQueue) { - if (typeof update === "function") { - flushStateUpdater(fiber, update, task); - } else { - flushStateUpdate(fiber, update, task); - } - - return; - } - - let updateToQueue: UpdateQueue; - - if (typeof update === "function") { - updateToQueue = createStateUpdater(update, task); - } else { - updateToQueue = createStateUpdate(update, task); - } - - enqueueUpdate(fiber, updateToQueue); - attemptToFlushUpdateQueue(fiber); -} - -export function enqueuePendingUpdate( - fiber: IStateFiber, - task: RunTask -) { - let shouldUseQueue = shouldEnqueueUpdate(fiber); - - // this means that the queue is populated or keepPending will tick now - if (!shouldUseQueue) { - flushPendingUpdate(fiber, task); - let hasKeepPending = (fiber.root.config?.keepPendingForMs || 0) > 0; - - if (hasKeepPending) { - let keepPendingDuration = fiber.root.config!.keepPendingForMs!; - ensureFiberQueueIsScheduled(fiber, keepPendingDuration); - } - - return; - } - - let updateToQueue = createPendingUpdate(task); - - enqueueUpdate(fiber, updateToQueue); - attemptToFlushUpdateQueue(fiber); -} - -export function isFunction(fn: any): fn is Function { - return typeof fn === "function"; -} diff --git a/packages/state-fiber/src/core/_types.ts b/packages/state-fiber/src/core/_types.ts deleted file mode 100644 index e975db63..00000000 --- a/packages/state-fiber/src/core/_types.ts +++ /dev/null @@ -1,276 +0,0 @@ -export type AbortFn = () => void; - -export type Fn = { - (props: FnProps): T | Promise; -}; - -export type RunEffect = "delay" | "debounce" | "throttle"; - -export type CacheConfig = { - enabled?: boolean; - hash?(args: A, payload: P): string; - deadline?: CacheEntryDeadline; // in Ms - - load?(): CachedStateList; - persist?(cache: CachedStateList): void; - - // todo: add onCacheLoad support -}; - -export type RetryConfig = { - enabled?: boolean; - max?: number; - backoff?: number; - retry(e: R, retryCounter: number): boolean; -}; - -export type CachedStateList = { - [hash: string]: CachedState; -}; - -export type CachedState = { - at: number; - state: SuccessState; -}; - -export type CacheEntryDeadline = - | number - | ((state: SuccessState) => number); - -export interface BaseFiberConfig { - initialValue?: T | ((cache: CachedStateList | null) => T); - - effect?: RunEffect; - effectDurationMs?: number; - - keepPendingForMs?: number; - skipPendingDelayMs?: number; - - reset?: boolean; - - retryConfig?: RetryConfig; - cacheConfig?: CacheConfig; -} - -export interface FnProps { - args: A; - payload: P; - - abort(): void; - signal: AbortSignal; - isAborted(): boolean; - onAbort(cb: () => void): void; - - // emit - // run - // select -} - -export interface StateRoot { - key: string; - - fn?: Fn; - config?: BaseFiberConfig; -} - -export interface IStateFiberRoot { - root: StateRoot; - bind(ctx: any): IStateFiber; -} - -export interface IStateFiber - extends IStateFiberRoot { - id: number; - version: number; - - payload: P; - state: State; // the current state - - context: ILibraryContext; - listeners: Map; // actual retainers - actions: IStateFiberActions; // wrapper to manipulate this fiber - - task: RunTask | null; // the latest executed task that completed - pending: RunTask | null; // the current pending task - - pendingRun: PendingRun | null; - pendingUpdate: PendingUpdate | null; - - queue: UpdateQueue | null; - queueId: ReturnType | null; - - cache: CachedStateList | null; -} - -export type FiberDataUpdate = T; -export type FiberErrorUpdate = R; -export type FiberDataUpdater = (prev: T) => T; -export type FiberStateUpdate = State; -export type FiberStateUpdater = ( - prev: State -) => State; - -export type UpdateQueue = - | { - kind: 0; // normal setData update - value: FiberDataUpdate; - task: RunTask | null; - next: UpdateQueue | null; - } - | { - kind: 1; // setData with function updater - value: FiberDataUpdater; - task: RunTask | null; - next: UpdateQueue | null; - } - | { - kind: 2; // setError - value: FiberErrorUpdate; - task: RunTask | null; - next: UpdateQueue | null; - } - | { - kind: 3; // normal setState update - value: FiberStateUpdate; - task: RunTask | null; - next: UpdateQueue | null; - } - | { - kind: 4; // setState with function updater - value: FiberStateUpdater; - task: RunTask | null; - next: UpdateQueue | null; - } - | { - kind: 5; // pending - task: RunTask; - next: UpdateQueue | null; - }; - -export type PendingRun = { - at: number; // datetime - id: ReturnType; - - // clean: () => void; -}; -export type PendingUpdate = { - at: number; // datetime - id: ReturnType; - task: RunTask; - // clean: () => void; -}; - -export interface ILibraryContext { - get(key: string): IStateFiber | undefined; - set(key: string, instance: IStateFiber): void; - remove(key: string): boolean; -} - -export type StateFiberUpdate = T | ((prev: T) => T); - -export interface IStateFiberActions { - run(...args: A): () => void; - runp(...args: A): Promise; - runc(props: RuncProps): () => void; - - setError(error: R): void; - setState( - state: State | ((prev: State) => State) - ): void; - setData(update: StateFiberUpdate): void; - - getState(): State; - - getPayload(): P; - mergePayload(p: Partial

): void; - - dispose(): void; - subscribe(cb: Function, data: any): () => void; -} - -export interface RuncProps { - args?: A; - payload?: P; - onError?(e: R): void; - onSuccess?(s: T): void; -} - -export type FiberPromise = - | PendingPromise - | FulfilledPromise - | RejectedPromise; - -export interface PendingPromise extends Promise { - status: "pending"; -} -export interface FulfilledPromise extends Promise { - value: T; - status: "fulfilled"; -} -export interface RejectedPromise extends Promise { - reason: R; - status: "rejected"; -} - -export interface RunTask { - args: A; - payload: P; - - at: number; // datetime - controller: AbortController; - result: T | Promise | null; - promise: FiberPromise | null; - - clean: () => void; - onAbort(cb: () => void): void; - indicators: { - aborted: boolean; - cleared: boolean; - }; - callbacks: ICallbacks | null; - - // when cache is supported, to avoid computing it again - hash: string | null; -} - -export interface ICallbacks { - onError?(e: R): void; - onSuccess?(s: T): void; -} - -export type InitialState = { - data?: T; - timestamp: number; - status: "initial"; -}; - -export type PendingState = { - timestamp: number; - status: "pending"; - props: SavedProps; - prev: InitialState | ErrorState | SuccessState; -}; - -export type ErrorState = { - error: R; - status: "error"; - timestamp: number; - props: SavedProps; -}; - -export type SuccessState = { - data: T; - timestamp: number; - status: "success"; - props: SavedProps; -}; - -export type SavedProps = { - args: A; - payload: P; -}; - -export type State = - | InitialState - | ErrorState - | SuccessState; diff --git a/packages/state-fiber/src/index-prod.js b/packages/state-fiber/src/index-prod.js deleted file mode 100644 index 95ac88c3..00000000 --- a/packages/state-fiber/src/index-prod.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict"; - -if (process.env.NODE_ENV === "production") { - module.exports = require("./state-fiber.production.js"); -} else { - module.exports = require("./state-fiber.development.js"); -} diff --git a/packages/state-fiber/src/index.ts b/packages/state-fiber/src/index.ts deleted file mode 100644 index 925cf43d..00000000 --- a/packages/state-fiber/src/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -export { - useFiber, - useData, - useAsync, - useQuery, - useParallel, - useMutation, - useStandalone, -} from "./react/FiberHooks"; - -export { FiberProvider } from "./react/FiberProvider"; - -export type { - ModernHooksReturn, - IAsyncProviderProps, - UseAsyncErrorReturn, - HooksStandardOptions, - UseAsyncSuccessReturn, - UseAsyncPendingReturn, - UseAsyncInitialReturn, -} from "./react/_types"; - -export type { - Fn, - State, - FnProps, - StateRoot, - ErrorState, - SuccessState, - PendingState, - InitialState, - BaseFiberConfig, - IStateFiberActions, -} from "./core/_types"; diff --git a/packages/state-fiber/src/react/FiberCommit.ts b/packages/state-fiber/src/react/FiberCommit.ts deleted file mode 100644 index 3b390ae2..00000000 --- a/packages/state-fiber/src/react/FiberCommit.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { - HooksStandardOptions, - IFiberSubscription, - IFiberSubscriptionAlternate, -} from "./_types"; -import { COMMITTED, CONCURRENT, SUSPENDING } from "./FiberSubscriptionFlags"; -import { isSuspending, resolveSuspendingPromise } from "./FiberSuspense"; -import { - dispatchNotification, - dispatchNotificationExceptFor, - togglePendingNotification, -} from "../core/FiberDispatch"; -import { IStateFiber } from "../core/_types"; -import { ensureSubscriptionIsUpToDate } from "./FiberSubscription"; -import { didDepsChange, emptyArray } from "../utils"; - -export function commitSubscription( - subscription: IFiberSubscription, - alternate: IFiberSubscriptionAlternate -) { - subscription.flags |= COMMITTED; - - let fiber = subscription.fiber; - let committedOptions = subscription.options; - - // if alternate is falsy, this means this subscription is ran again - // without the component rendering (StrictEffects, Offscreen .. ) - // we only assign the most recent alternate - // being not the most recent means that react threw a render without completing - // it, and came back to an old tree then displayed it again - if (alternate) { - // merge all alternate properties inside the subscription - Object.assign(subscription, alternate); - // only un-reference the most recent alternate - // this inequality means that react in the mean time is preparing a render - // in OffScreen mode - if (subscription.alternate === alternate) { - subscription.alternate = null; - } - } - - if (subscription.version !== fiber.version) { - let didScheduleUpdate = ensureSubscriptionIsUpToDate(subscription); - // this means that subscription was stale and did schedule an update - // to rerender the component. no need to perform subscription since - // we will be rendering again - if (didScheduleUpdate) { - return; - } - } - - let unsubscribe = fiber.actions.subscribe(subscription.update, subscription); - - if (subscription.flags & CONCURRENT) { - commitConcurrentSubscription(subscription); - } else { - commitLegacySubscription(subscription, committedOptions); - } - - return () => { - unsubscribe(); - subscription.flags &= ~COMMITTED; - }; -} - -function commitConcurrentSubscription( - subscription: IFiberSubscription -) { - let fiber = subscription.fiber; - if (fiber.task) { - let latestResolvedPromise = fiber.task.promise; - if (latestResolvedPromise) { - let suspendingUpdater = isSuspending(latestResolvedPromise); - // if this component was the one that "suspended", when it commits again - // it will be responsible for notifying the others - if ( - suspendingUpdater && - (suspendingUpdater === subscription.update || - // suspendingUpdater is garbage collected happens when a component - // suspends on the initial render, because react does not preserve - // the identity of the state and creates another. - // in this case, in first committing subscription will notify others - // this does not happen on updates - wasSuspenderGarbageCollected(fiber, suspendingUpdater)) - ) { - subscription.flags &= ~SUSPENDING; - resolveSuspendingPromise(latestResolvedPromise); - dispatchNotificationExceptFor(fiber, suspendingUpdater); - } - } - } -} - -function commitLegacySubscription( - subscription: IFiberSubscription, - prevOptions: HooksStandardOptions -) { - let shouldRun = shouldLegacySubscriptionRun(subscription, prevOptions); - if (shouldRun) { - let fiber = subscription.fiber; - let options = subscription.options; - let renderRunArgs = (options.args || emptyArray) as A; - - // we enable notifications about "pending" updates via this - let previousNotification = togglePendingNotification(true); - - fiber.actions.run.apply(null, renderRunArgs); - - togglePendingNotification(previousNotification); - dispatchNotification(fiber); - } -} - -function shouldLegacySubscriptionRun( - subscription: IFiberSubscription, - prevOptions: HooksStandardOptions -) { - let nextOptions = subscription.options; - if (!nextOptions || nextOptions.lazy !== false) { - return false; - } - - let fiber = subscription.fiber; - let prevArgs = prevOptions.args || emptyArray; - let nextArgs = nextOptions.args || emptyArray; - - if (fiber.pending) { - let pendingPromise = fiber.pending.promise!; - if (isSuspending(pendingPromise)) { - return false; - } - - let pendingArgs = fiber.pending.args; - return didDepsChange(pendingArgs, nextArgs); - } - - let state = fiber.state; - if (state.status === "initial") { - return true; - } - - let fiberCurrentArgs = state.props.args; - let didArgsChange = didDepsChange(fiberCurrentArgs, nextArgs); - - if (didArgsChange) { - return didDepsChange(prevArgs, nextArgs); - } -} - -function wasSuspenderGarbageCollected( - fiber: IStateFiber, - updater -) { - for (let t of fiber.listeners.keys()) { - if (t === updater) { - return false; - } - } - return true; -} diff --git a/packages/state-fiber/src/react/FiberHooks.ts b/packages/state-fiber/src/react/FiberHooks.ts deleted file mode 100644 index 97d85364..00000000 --- a/packages/state-fiber/src/react/FiberHooks.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from "react"; -import { - HooksStandardOptions, - LegacyHooksReturn, - ModernHooksReturn, -} from "./_types"; -import { useCurrentContext } from "./FiberProvider"; -import { - inferLegacySubscriptionReturn, - inferModernSubscriptionReturn, - useSubscription, -} from "./FiberSubscription"; -import { renderFiber, resolveFiber } from "./FiberRender"; -import { commitSubscription } from "./FiberCommit"; -import { COMMITTED, USE_ASYNC, USE_FIBER } from "./FiberSubscriptionFlags"; -import { IStateFiberActions } from "../core/_types"; -import { guardAgainstInfiniteLoop } from "../utils"; - -// useAsync suspends on pending and throws when error, like React.use() -export function useAsync( - options: HooksStandardOptions -): ModernHooksReturn { - let context = useCurrentContext(options); - let fiber = resolveFiber(context, options); - - let subscription = useSubscription(USE_ASYNC, fiber, options); - let alternate = renderFiber(USE_ASYNC, subscription, options); - React.useLayoutEffect(() => commitSubscription(subscription, alternate)); - alternate.return = inferModernSubscriptionReturn(subscription, alternate); - - alternate.version = fiber.version; - - return alternate.return; -} - -// will not throw on pending and error -// will give the all statuses, this is the old useAsyncState :) -// full backward compatibility will be smooth -export function useFiber( - options: HooksStandardOptions -): LegacyHooksReturn { - let context = useCurrentContext(options); - let fiber = resolveFiber(context, options); - - let subscription = useSubscription(USE_FIBER, fiber, options); - let alternate = renderFiber(USE_FIBER, subscription, options); - - React.useLayoutEffect(() => commitSubscription(subscription, alternate)); - alternate.return = inferLegacySubscriptionReturn(subscription, alternate); - - // console.log("useFiber", { ...alternate.return, source: null }); - alternate.version = fiber.version; - - // guardAgainstInfiniteLoop(); - - console.log("hehehe", fiber.cache); - return alternate.return; -} - -// will return data (T) directly, no selector -// it will suspend and throw on error -// it will also return the fiber source so manipulation is possible -export function useData( - options: HooksStandardOptions -): [T, IStateFiberActions] { - let result = useAsync(options); - return [result.data, result.source]; -} - -// will mark the fiber as a query: predefined opinionated configuration -// similar to other libraries such react-query, rtk-query and apollo -export function useQuery() {} - -// will use a similar fiber to the inferred from options -// but will not impact it, it will fork it and use it -export function useParallel() {} - -// will use a parallel fiber scoped to this very component -// it won't be shared to context; but should be hydrated if needed -export function useStandalone() {} - -// a mutation is a normal Fiber, but with dependencies that are invalidated -// once a "success" event occurs in the mutation -export function useMutation() {} diff --git a/packages/state-fiber/src/react/FiberProvider.tsx b/packages/state-fiber/src/react/FiberProvider.tsx deleted file mode 100644 index 63393f0e..00000000 --- a/packages/state-fiber/src/react/FiberProvider.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import { IAsyncContext, IAsyncProviderProps, HooksStandardOptions } from "./_types"; -import { requestContext } from "../core/FiberContext"; -import { ILibraryContext } from "../core/_types"; - -export const AsyncContext = React.createContext(null); - -export function FiberProvider({ children, ctx }: IAsyncProviderProps) { - let ctxToUse = ctx ?? null; - let context = requestContext(ctxToUse); - - return ( - {children} - ); -} - -export function useCurrentContext( - options: HooksStandardOptions -): ILibraryContext { - let reactContext = React.useContext(AsyncContext); - let desiredContext = typeof options === "object" ? options.context : null; - - if (desiredContext) { - return requestContext(desiredContext); - } - - if (reactContext) { - return reactContext; - } - - return requestContext(null); // global context -} diff --git a/packages/state-fiber/src/react/FiberRender.ts b/packages/state-fiber/src/react/FiberRender.ts deleted file mode 100644 index 51367c7c..00000000 --- a/packages/state-fiber/src/react/FiberRender.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { BaseFiberConfig, ILibraryContext, IStateFiber } from "../core/_types"; -import { - HooksStandardOptions, - IFiberSubscription, - IFiberSubscriptionAlternate, -} from "./_types"; -import { isSuspending, registerSuspendingPromise } from "./FiberSuspense"; -import { didDepsChange, emptyArray } from "../utils"; -import { StateFiber } from "../core/Fiber"; -import { - CONCURRENT, - SUSPENDING, - THROW_ON_ERROR, -} from "./FiberSubscriptionFlags"; -import { - completeRenderPhaseRun, - startRenderPhaseRun, -} from "./FiberSubscription"; -import { - dispatchNotificationExceptFor, - togglePendingNotification, -} from "../core/FiberDispatch"; - -export function renderFiber( - renderFlags: number, - subscription: IFiberSubscription, - options: HooksStandardOptions -): IFiberSubscriptionAlternate { - let fiber = subscription.fiber; - let alternate = subscription.alternate; - // the alternate presence means that we started rendering without committing - // this is either because of StrictMode, Offscreen or we prepared the - // alternate so this path would be faster - if (!alternate || alternate.version !== fiber.version) { - alternate = { - options, - flags: renderFlags, - return: subscription.return, - version: subscription.version, - }; - } - - if (renderFlags & CONCURRENT) { - renderFiberConcurrent(renderFlags, subscription, alternate); - } - - if (renderFlags & THROW_ON_ERROR && fiber.state.status === "error") { - throw fiber.state.error; - } - - return alternate; -} - -function renderFiberConcurrent( - renderFlags: number, - subscription: IFiberSubscription, - alternate: IFiberSubscriptionAlternate -) { - let fiber = subscription.fiber; - let options = alternate.options; - let shouldRun = shouldRunOnRender(subscription, alternate); - - if (shouldRun) { - let renderRunArgs = getRenderRunArgs(options); - let wasRunningOnRender = startRenderPhaseRun(); - // we stop notifications for pending on render - let wasNotifyingOnPending = togglePendingNotification(false); - - fiber.actions.run.apply(null, renderRunArgs); - - // this means that this render will suspender, so we notify other subscribers - // since the commit phase won't get executed for this component, so basically - // the outside world is unaware of this update - if (renderFlags & CONCURRENT && fiber.pending) { - dispatchNotificationExceptFor(fiber, subscription.update); - } - - completeRenderPhaseRun(wasRunningOnRender); - togglePendingNotification(wasNotifyingOnPending); - } - - if (fiber.pending) { - subscription.flags |= SUSPENDING; - let promise = fiber.pending.promise!; - - registerSuspendingPromise(promise, subscription.update); - - throw promise; - } - let previousPromise = fiber.task?.promise; - if (previousPromise && isSuspending(previousPromise)) { - // we mark it as suspending, this means that this promise - // was previously suspending a tree. - // Since we don't know when/if react will recover from suspense, - // we mark the alternate as being suspending, so in commit phase, - // we will notify subscribers - if (!(alternate.flags & SUSPENDING)) { - alternate.flags |= SUSPENDING; - } - } -} - -function getRenderRunArgs( - options: HooksStandardOptions -) { - if (!options) { - return emptyArray as unknown as A; - } - return (options.args || emptyArray) as A; -} - -function shouldRunOnRender( - subscription: IFiberSubscription, - alternate: IFiberSubscriptionAlternate -) { - let nextOptions = alternate.options; - - if (nextOptions && typeof nextOptions === "object") { - let fiber = subscription.fiber; - let prevOptions = subscription.options; - - if (nextOptions.lazy === false) { - let state = fiber.state; - let nextArgs = nextOptions.args || emptyArray; - - if (fiber.pending) { - // if already pending, re-run only if args changed - let pendingArgs = fiber.pending.args; - return didDepsChange(pendingArgs, nextArgs); - } - - if (state.status === "initial") { - return true; - } - - let fiberCurrentArgs = state.props.args; - let didArgsChange = didDepsChange(fiberCurrentArgs, nextArgs); - - if (didArgsChange) { - let didArgsChange = didDepsChange( - prevOptions.args || emptyArray, - nextArgs - ); - return didArgsChange; - } - } - } - - return false; -} - -export function resolveFiber( - context: ILibraryContext, - options: HooksStandardOptions -) { - if (typeof options === "object") { - let { key, producer } = options; - let existingFiber = context.get(key); - let fiberConfig = sliceFiberConfig(options); - - if (existingFiber) { - reconcileFiberConfig(existingFiber, fiberConfig); - return existingFiber as IStateFiber; - } - - let newFiber = new StateFiber( - { key, config: fiberConfig, fn: producer }, - context - ); - context.set(key, newFiber); // todo: not always (standalone ;)) - return newFiber as IStateFiber; - } - throw new Error("Not supported yet"); -} - -function sliceFiberConfig( - options: HooksStandardOptions -): BaseFiberConfig | undefined { - if (!options || typeof options !== "object") { - return undefined; - } - return options; -} - -function reconcileFiberConfig( - fiber: IStateFiber, - config: BaseFiberConfig | undefined -) { - if (!fiber.root.config) { - Object.assign({}, config); - } else { - Object.assign(fiber.root.config, config); - } -} diff --git a/packages/state-fiber/src/react/FiberSubscription.ts b/packages/state-fiber/src/react/FiberSubscription.ts deleted file mode 100644 index 098ae697..00000000 --- a/packages/state-fiber/src/react/FiberSubscription.ts +++ /dev/null @@ -1,436 +0,0 @@ -import * as React from "react"; -import { - ErrorState, - InitialState, - IStateFiber, - PendingState, - SuccessState, -} from "../core/_types"; -import { - HooksStandardOptions, - IFiberSubscription, - IFiberSubscriptionAlternate, - LegacyHooksReturn, - ModernHooksReturn, - UseAsyncErrorReturn, - UseAsyncInitialReturn, - UseAsyncPendingReturn, - UseAsyncSuccessReturn, -} from "./_types"; -import { __DEV__, didDepsChange, resolveComponentName } from "../utils"; -import { isSuspending } from "./FiberSuspense"; -import { SUSPENDING } from "./FiberSubscriptionFlags"; - -let ZERO = 0; -export function useSubscription( - flags: number, - fiber: IStateFiber, - options: HooksStandardOptions -): IFiberSubscription { - let [, start] = React.useTransition(); - let [, update] = React.useState(ZERO); - - // a subscription is a function, we use a stateUpdater as identity - // since it is created and bound to the react state hook, so stable - // and we use it as a source of truth. - // this updater doesn't get retained until it gets committed - let previousSubscription = fiber.listeners.get(update); - - if (previousSubscription) { - // the S part may be wrong during render, but alternate will commit it - return previousSubscription as IFiberSubscription; - } - - let subscription: IFiberSubscription = { - flags, - fiber, - start, - update, - options, - return: null, - callback: null, - alternate: null, - version: fiber.version, - }; - subscription.callback = () => onFiberStateChange(subscription); - if (__DEV__) { - subscription.at = resolveComponentName(); - } - return subscription; -} - -export function inferModernSubscriptionReturn( - subscription: IFiberSubscription, - alternate: IFiberSubscriptionAlternate -): ModernHooksReturn { - let fiber = subscription.fiber; - if (!alternate.return || alternate.version !== fiber.version) { - let state = fiber.state; - let value: S | null = null; - - if (state.status !== "error") { - value = selectStateFromFiber(fiber, alternate.options); - } - - return createModernSubscriptionReturn(fiber, value); - } - - let prevOptions = subscription.options; - let pendingOptions = alternate.options; - - // this means that we need to check what changed in the options - // this will be always false if the user is provider an object/fn literal - if (prevOptions !== pendingOptions) { - // at this point, we need to check the relevant options that may impact - // the returned value. The impacting options are: - // - select: well, that's what we select - // - areEqual - let prevSelector = prevOptions.selector; - let nextSelector = pendingOptions.selector; - - let nextAreEqual = pendingOptions.areEqual; - - if (nextSelector && prevSelector !== nextSelector) { - let selectedValue = nextSelector(fiber.state); - - let equalityFn = nextAreEqual ? nextAreEqual : Object.is; - - if (!equalityFn(selectedValue, alternate.return.data as S)) { - return createModernSubscriptionReturn(fiber, selectedValue); - } - } - } - - return alternate.return as ModernHooksReturn; -} - -export function inferLegacySubscriptionReturn( - subscription: IFiberSubscription, - alternate: IFiberSubscriptionAlternate -): LegacyHooksReturn { - let fiber = subscription.fiber; - if (!alternate.return || alternate.version !== fiber.version) { - let state = fiber.state; - let value: S | null = null; - - if (state.status !== "error") { - value = selectStateFromFiber(fiber, alternate.options); - } - - return createLegacySubscriptionReturn(fiber, value); - } - - let prevOptions = subscription.options; - let pendingOptions = alternate.options; - - // this means that we need to check what changed in the options - // this will be always false if the user is provider an object/fn literal - if (prevOptions !== pendingOptions) { - // at this point, we need to check the relevant options that may impact - // the returned value. The impacting options are: - // - select: well, that's what we select - // - areEqual - let prevSelector = prevOptions.selector; - let nextSelector = pendingOptions.selector; - - let nextAreEqual = pendingOptions.areEqual; - - if (nextSelector && prevSelector !== nextSelector) { - let selectedValue = nextSelector(fiber.state); - - let equalityFn = nextAreEqual ? nextAreEqual : Object.is; - - if (!equalityFn(selectedValue, alternate.return.data)) { - return createLegacySubscriptionReturn(fiber, selectedValue); - } - } - } - return alternate.return; -} - -function createLegacySubscriptionReturn( - fiber: IStateFiber, - value: S | null -): LegacyHooksReturn { - let state = fiber.state; - if (fiber.pending) { - let pending = fiber.pending; - const pendingState: PendingState = { - prev: state, - status: "pending", - timestamp: Date.now(), - props: { payload: pending.payload, args: pending.args }, - }; - return createSubscriptionPendingReturn(pendingState, fiber, value as S); - } - - switch (state.status) { - case "error": { - return createSubscriptionErrorReturn(state, fiber); - } - case "initial": { - return createSubscriptionInitialReturn(state, fiber, value as S); - } - case "success": { - return createSubscriptionSuccessReturn(state, fiber, value as S); - } - default: { - throw new Error("This is a bug"); - } - } -} -function createModernSubscriptionReturn( - fiber: IStateFiber, - value: S | null -): ModernHooksReturn { - switch (fiber.state.status) { - case "initial": { - return createSubscriptionInitialReturn(fiber.state, fiber, value as S); - } - case "success": { - return createSubscriptionSuccessReturn(fiber.state, fiber, value as S); - } - case "error": - default: { - throw new Error("This is a bug"); - } - } -} - -function createSubscriptionInitialReturn( - state: InitialState, - fiber: IStateFiber, - value: S -): UseAsyncInitialReturn { - return { - state, - error: null, - data: value, - isError: false, - isInitial: true, - isPending: false, - isSuccess: false, - source: fiber.actions, - }; -} - -function createSubscriptionPendingReturn( - state: PendingState, - fiber: IStateFiber, - value: S -): UseAsyncPendingReturn { - return { - state, - data: value, - error: null, - isError: false, - isInitial: false, - isPending: true, - isSuccess: false, - source: fiber.actions, - }; -} - -function createSubscriptionSuccessReturn( - state: SuccessState, - fiber: IStateFiber, - value: S -): UseAsyncSuccessReturn { - return { - state, - error: null, - data: value, - isError: false, - isInitial: false, - isPending: false, - isSuccess: true, - source: fiber.actions, - }; -} - -function createSubscriptionErrorReturn( - state: ErrorState, - fiber: IStateFiber -): UseAsyncErrorReturn { - return { - state, - data: null, - isError: true, - isInitial: false, - isPending: false, - isSuccess: false, - error: state.error, - source: fiber.actions, - }; -} - -function selectStateFromFiber( - fiber: IStateFiber, - options: HooksStandardOptions | null -): S { - if (options && typeof options === "object" && options.selector) { - return options.selector(fiber.state); - } - - if (fiber.state.status === "error") { - throw new Error("This is a bug"); - } - - return fiber.state.data as S; -} - -export function onFiberStateChange( - subscription: IFiberSubscription -) { - let { fiber, version, return: committedReturn } = subscription; - if (!committedReturn) { - throw new Error("This is a bug"); - } - if (fiber.version === version) { - return; - } - - let finishedTask = fiber.task; - let currentSuspender = - finishedTask && finishedTask.promise && isSuspending(finishedTask.promise); - - // this means that the fiber's most recent "task" was "suspending" - if (currentSuspender) { - let isThisSubscriptionSuspending = currentSuspender === subscription.update; - if (isThisSubscriptionSuspending) { - // leave react recover it and ignore this notification - // We need to schedule an auto-recovery if react stops rendering the - // suspending tree. This can happen, the delay is TDB (~50-100ms) - return; - } - let isNotPending = !fiber.pending; - let wasCommittingPending = subscription.return!.isPending; - if (isNotPending && wasCommittingPending) { - // this is case do nothing, because since it was suspending - // the suspender will recover back using react suspense and then it will - // notify other components from its commit effect - return; - } - } - - ensureSubscriptionIsUpToDate(subscription); -} - -// returns true if it did schedule an update for this component -export function ensureSubscriptionIsUpToDate( - subscription: IFiberSubscription -): boolean { - let { fiber, return: committedReturn } = subscription; - - // this may never happen, but let's keep it safe - if (!committedReturn) { - return false; - } - - let state = fiber.state; - if (state.status === "error") { - let didReturnError = committedReturn.isError; - let didErrorChange = state.error !== committedReturn.error; - if (!didReturnError || didErrorChange) { - subscription.update(forceComponentUpdate); - return true; - } - } else { - let willBePending = fiber.pending; - let isSubscriptionSuspending = subscription.flags & SUSPENDING; - if (willBePending && isSubscriptionSuspending) { - // no need to resuspend - return false; - } - // at this point, state is either Initial or Success - let newValue = selectSubscriptionData(fiber, state, subscription); - - let areEqual = subscription.options.areEqual; - let equalityFn = areEqual ? areEqual : Object.is; - - if ( - !equalityFn(newValue, committedReturn.data) || - !doesPreviousReturnMatchFiberStatus(fiber, state.status, committedReturn) - ) { - if (isRenderPhaseRun) { - queueMicrotask(() => { - subscription.update(forceComponentUpdate); - }); - } else { - subscription.update(forceComponentUpdate); - } - return true; - } - } - return false; -} - -function forceComponentUpdate(prev: number) { - return prev + 1; -} - -// this will check whether the returned version (or the committed one) -// is synced with fiber's state, including pending -// keep it stupid -export function doesPreviousReturnMatchFiberStatus< - T, - A extends unknown[], - R, - P, - S ->( - fiber: IStateFiber, - status: "initial" | "success", - previousReturn: LegacyHooksReturn -) { - if (fiber.pending && previousReturn.isPending) { - let nextArgs = fiber.pending.args; - let prevArgs = previousReturn.state.props.args; - let didArgsChange = didDepsChange(prevArgs, nextArgs); - // if args changed, return doesnt match anymore optimistic, schedule update - return !didArgsChange; - } - if (fiber.pending && !previousReturn.isPending) { - return false; - } - if (previousReturn.isError) { - return false; - } - if (previousReturn.isInitial && status !== "initial") { - return false; - } - if (previousReturn.isSuccess && status !== "success") { - return false; - } - return true; -} - -export function selectSubscriptionData( - fiber: IStateFiber, - state: InitialState | SuccessState, - subscription: IFiberSubscription -) { - let options = subscription.options; - if (options && options.selector) { - return options.selector(state); - } - if (fiber.pending) { - let previousState = fiber.state; - if (previousState.status === "error") { - return null; - } - return previousState.data as S; - } else { - return state.data as S; - } -} - -let isRenderPhaseRun = false; -export function startRenderPhaseRun(): boolean { - let prev = isRenderPhaseRun; - isRenderPhaseRun = true; - return prev; -} -export function completeRenderPhaseRun(prev) { - isRenderPhaseRun = prev; -} diff --git a/packages/state-fiber/src/react/FiberSubscriptionFlags.ts b/packages/state-fiber/src/react/FiberSubscriptionFlags.ts deleted file mode 100644 index 14732bb4..00000000 --- a/packages/state-fiber/src/react/FiberSubscriptionFlags.ts +++ /dev/null @@ -1,42 +0,0 @@ -// these flags will be used to give capabilities to hooks - -import { __DEV__ } from "../utils"; - -export const LEGACY /* */ = 0b0000_0000_0001; // Old react style -export const CONCURRENT /* */ = 0b0000_0000_0010; // Suspense -export const THROW_ON_ERROR /* */ = 0b0000_0000_0100; // error boundary -export const TRANSITION /* */ = 0b0000_0000_1000; // transitions - -export const SUSPENDING /* */ = 0b0000_0001_0000; // threw on render -export const COMMITTED /* */ = 0b0000_0010_0000; // painted on screen - - -export const USE_FIBER = LEGACY; -export const USE_ASYNC = CONCURRENT | THROW_ON_ERROR | TRANSITION; - -export let humanizeFlags; -/* istanbul ignore next */ -if (__DEV__) { - humanizeFlags = (flags: number) => { - let output: string[] = []; - if (flags & LEGACY) { - output.push("LEGACY"); - } - if (flags & CONCURRENT) { - output.push("CONCURRENT"); - } - if (flags & THROW_ON_ERROR) { - output.push("THROW_ON_ERROR"); - } - if (flags & TRANSITION) { - output.push("TRANSITION"); - } - if (flags & SUSPENDING) { - output.push("SUSPENDING"); - } - if (flags & COMMITTED) { - output.push("COMMITTED"); - } - return output; - }; -} diff --git a/packages/state-fiber/src/react/FiberSuspense.ts b/packages/state-fiber/src/react/FiberSuspense.ts deleted file mode 100644 index a9270880..00000000 --- a/packages/state-fiber/src/react/FiberSuspense.ts +++ /dev/null @@ -1,13 +0,0 @@ -let suspendingPromises = new WeakMap, Function>(); -export function registerSuspendingPromise(promise: Promise, fn) { - if (suspendingPromises.has(promise)) { - return; - } - suspendingPromises.set(promise, fn); -} -export function resolveSuspendingPromise(promise: Promise) { - suspendingPromises.delete(promise); -} -export function isSuspending(promise: Promise) { - return suspendingPromises.get(promise); -} diff --git a/packages/state-fiber/src/react/_types.ts b/packages/state-fiber/src/react/_types.ts deleted file mode 100644 index f0848291..00000000 --- a/packages/state-fiber/src/react/_types.ts +++ /dev/null @@ -1,105 +0,0 @@ -import * as React from "react"; -import { - BaseFiberConfig, - ErrorState, - Fn, - ILibraryContext, - InitialState, - IStateFiber, - IStateFiberActions, - PendingState, - State, - SuccessState, -} from "../core/_types"; - -export type IAsyncContext = ILibraryContext; - -export interface IAsyncProviderProps { - ctx?: any; - children: React.ReactNode; -} - -export interface HooksStandardOptions - extends BaseFiberConfig { - args?: A; - - key: string; - context?: any; - lazy?: boolean; - producer?: Fn; - selector?: (p: State) => S; - areEqual?: (prevSelectedValue: S, nextValue: S) => boolean; -} - -export type ModernHooksReturn = - | UseAsyncInitialReturn - | UseAsyncSuccessReturn; - -export type LegacyHooksReturn = - | UseAsyncInitialReturn - | UseAsyncPendingReturn - | UseAsyncSuccessReturn - | UseAsyncErrorReturn; - -export interface IFiberSubscriptionAlternate { - flags: number; - version: number; - options: HooksStandardOptions; - return: LegacyHooksReturn | null; -} - -export interface IFiberSubscription - extends IFiberSubscriptionAlternate { - at?: string, - // static per subscription - callback: Function | null; - fiber: IStateFiber; - start: React.TransitionStartFunction; - update: React.Dispatch>; - - alternate: null | IFiberSubscriptionAlternate; -} - -export type UseAsyncErrorReturn = { - state: ErrorState; - data: null; - isError: true; - isInitial: false; - isPending: false; - isSuccess: false; - error: R; - source: IStateFiberActions; -}; - -export type UseAsyncInitialReturn = { - state: InitialState; - data: S; - isError: false; - isInitial: true; - isPending: false; - isSuccess: false; - error: null; - source: IStateFiberActions; -}; - -export type UseAsyncPendingReturn = { - state: PendingState; - data: S; - isError: false; - isInitial: false; - isPending: true; - isSuccess: false; - error: null; - source: IStateFiberActions; -}; - -export type UseAsyncSuccessReturn = { - state: SuccessState; - data: S; - isError: false; - isInitial: false; - isPending: false; - isSuccess: true; - error: null; - source: IStateFiberActions; -}; diff --git a/packages/state-fiber/src/utils.ts b/packages/state-fiber/src/utils.ts deleted file mode 100644 index 39cb16cf..00000000 --- a/packages/state-fiber/src/utils.ts +++ /dev/null @@ -1,47 +0,0 @@ -export const __DEV__ = process.env.NODE_ENV !== "production"; - -export function noop() {} - -export function isPromise(candidate): candidate is Promise { - return !!candidate && typeof candidate.then === "function"; -} - -export const emptyArray = []; - -export function resolveComponentName() { - const stackTrace = new Error().stack; - if (!stackTrace) { - return undefined; - } - - const regex = new RegExp(/at.(\w+).*$/, "gm"); - let match = regex.exec(stackTrace); - - let i = 0; - while (i < 3 && match) { - match = regex.exec(stackTrace); - - i += 1; - } - - return match?.[1]; -} - -export function didDepsChange(deps: any[], deps2: any[]) { - if (deps.length !== deps2.length) { - return true; - } - for (let i = 0, { length } = deps; i < length; i += 1) { - if (!Object.is(deps[i], deps2[i])) { - return true; - } - } - return false; -} - -let renders = 0; -export function guardAgainstInfiniteLoop() { - if (++renders > 10) { - throw new Error("Stop"); - } -} diff --git a/packages/state-fiber/tsconfig.json b/packages/state-fiber/tsconfig.json deleted file mode 100644 index eeddf4f0..00000000 --- a/packages/state-fiber/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "target": "ESNext", - "baseUrl": ".", - "types": ["node", "jest"], - "paths": { - "tslib": ["node_modules/tslib"] - } - }, - "include": ["src"], - "exclude": [ - "node_modules", - "src/index-prod.js" - ] -} diff --git a/packages/state-fiber/tsconfig.test.json b/packages/state-fiber/tsconfig.test.json deleted file mode 100644 index 16fb92c3..00000000 --- a/packages/state-fiber/tsconfig.test.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "types": ["node", "jest"], - "moduleResolution": "Node", - "module": "ESNext" - }, - "include": ["src"], - "exclude": [ - "node_modules", - "src/__tests__", - "src/index-prod.js" - ] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30e0fb30..205e7ab8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -414,133 +414,6 @@ importers: specifier: ^1.5.15 version: 1.5.15(ts-node@10.9.1)(typescript@4.9.5) - packages/react-async-states-utils: - dependencies: - react: - specifier: ^16.8.0 || ^17.0.2 || ^18.0.0 - version: 18.2.0 - devDependencies: - '@babel/plugin-proposal-class-properties': - specifier: ^7.18.6 - version: 7.18.6(@babel/core@7.21.0) - '@babel/preset-env': - specifier: ^7.20.2 - version: 7.20.2(@babel/core@7.21.0) - '@babel/preset-react': - specifier: ^7.18.6 - version: 7.18.6(@babel/core@7.21.0) - '@babel/preset-typescript': - specifier: ^7.18.6 - version: 7.18.6(@babel/core@7.21.0) - '@jest/globals': - specifier: ^29.3.1 - version: 29.3.1 - '@rollup/plugin-babel': - specifier: ^6.0.2 - version: 6.0.3(@babel/core@7.21.0)(rollup@3.7.4) - '@rollup/plugin-commonjs': - specifier: ^23.0.2 - version: 23.0.5(rollup@3.7.4) - '@rollup/plugin-json': - specifier: ^5.0.1 - version: 5.0.2(rollup@3.7.4) - '@rollup/plugin-node-resolve': - specifier: ^15.0.1 - version: 15.0.1(rollup@3.7.4) - '@rollup/plugin-replace': - specifier: ^5.0.1 - version: 5.0.1(rollup@3.7.4) - '@rollup/plugin-terser': - specifier: ^0.1.0 - version: 0.1.0(rollup@3.7.4) - '@testing-library/dom': - specifier: ^8.19.0 - version: 8.19.0 - '@testing-library/react': - specifier: 13.4.0 - version: 13.4.0(react-dom@18.2.0)(react@18.2.0) - '@testing-library/react-hooks': - specifier: ^8.0.1 - version: 8.0.1(@types/react@18.0.25)(react-dom@18.2.0)(react-test-renderer@18.2.0)(react@18.2.0) - '@types/jest': - specifier: ^29.2.4 - version: 29.2.4 - '@types/node': - specifier: ^18.11.9 - version: 18.11.15 - '@types/react': - specifier: 18.0.25 - version: 18.0.25 - async-states: - specifier: workspace:^2.0.0-alpha-6 - version: link:../core - babel-jest: - specifier: ^29.3.1 - version: 29.3.1(@babel/core@7.21.0) - circular-dependency-plugin: - specifier: ^5.2.2 - version: 5.2.2(webpack@5.75.0) - cross-env: - specifier: ^7.0.3 - version: 7.0.3 - eslint-config-standard: - specifier: 17.0.0 - version: 17.0.0(eslint-plugin-import@2.26.0)(eslint-plugin-n@15.6.0)(eslint-plugin-promise@6.1.1)(eslint@8.39.0) - eslint-config-standard-react: - specifier: 12.0.0 - version: 12.0.0(eslint-plugin-react@7.31.11)(eslint@8.39.0) - eslint-plugin-import: - specifier: ^2.13.0 - version: 2.26.0(@typescript-eslint/parser@5.46.1)(eslint-import-resolver-typescript@3.5.5)(eslint@8.39.0) - eslint-plugin-react: - specifier: ^7.10.0 - version: 7.31.11(eslint@8.39.0) - jest: - specifier: ^29.3.1 - version: 29.3.1(@types/node@18.11.15)(ts-node@10.9.1) - jest-environment-jsdom: - specifier: ^29.3.1 - version: 29.3.1 - react-async-states: - specifier: workspace:^2.0.0-alpha-6 - version: link:../react-async-states - react-test-renderer: - specifier: 18.2.0 - version: 18.2.0(react@18.2.0) - rimraf: - specifier: ^3.0.2 - version: 3.0.2 - rollup: - specifier: ^3.3.0 - version: 3.7.4 - rollup-plugin-copy: - specifier: ^3.4.0 - version: 3.4.0 - rollup-plugin-delete: - specifier: ^2.0.0 - version: 2.0.0 - rollup-plugin-dts: - specifier: ^5.0.0 - version: 5.0.0(rollup@3.7.4)(typescript@4.9.5) - rollup-plugin-gzip: - specifier: 3.1.0 - version: 3.1.0(rollup@3.7.4) - rollup-plugin-sourcemaps: - specifier: ^0.6.3 - version: 0.6.3(@types/node@18.11.15)(rollup@3.7.4) - rollup-plugin-typescript2: - specifier: ^0.34.1 - version: 0.34.1(rollup@3.7.4)(typescript@4.9.5) - ts-jest: - specifier: ^29.0.3 - version: 29.0.3(@babel/core@7.21.0)(babel-jest@29.3.1)(jest@29.3.1)(typescript@4.9.5) - tslib: - specifier: ^2.4.1 - version: 2.4.1 - ttypescript: - specifier: ^1.5.15 - version: 1.5.15(ts-node@10.9.1)(typescript@4.9.5) - packages/state-fiber: devDependencies: '@babel/plugin-proposal-class-properties': @@ -1059,18 +932,6 @@ packages: '@babel/helper-annotate-as-pure': 7.18.6 regexpu-core: 5.2.2 - /@babel/helper-create-regexp-features-plugin@7.22.9(@babel/core@7.20.5): - resolution: {integrity: sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.20.5 - '@babel/helper-annotate-as-pure': 7.22.5 - regexpu-core: 5.3.2 - semver: 6.3.1 - dev: false - /@babel/helper-create-regexp-features-plugin@7.22.9(@babel/core@7.21.0): resolution: {integrity: sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==} engines: {node: '>=6.9.0'} @@ -1904,8 +1765,8 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.5 - '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.20.5) - '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-create-regexp-features-plugin': 7.20.5(@babel/core@7.20.5) + '@babel/helper-plugin-utils': 7.20.2 dev: false /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.0):