increment
@@ -142,7 +142,7 @@ describe('should do basic subscription to an async state', () => {
fireEvent.click(incrementBtn);
});
// pending state is now skipped!
- expect(screen.getByTestId("status").innerHTML).toEqual(AsyncStateStatus.initial);
+ expect(screen.getByTestId("status").innerHTML).toEqual(Status.initial);
expect(screen.getByTestId("result").innerHTML).toEqual("0");
expect(screen.getByTestId("pending").innerHTML).toEqual("");
@@ -151,7 +151,7 @@ describe('should do basic subscription to an async state', () => {
});
// pending state is now !!
- expect(screen.getByTestId("status").innerHTML).toEqual(AsyncStateStatus.pending);
+ expect(screen.getByTestId("status").innerHTML).toEqual(Status.pending);
expect(screen.getByTestId("result").innerHTML).toEqual("");
expect(screen.getByTestId("pending").innerHTML).toEqual(pendingText);
@@ -159,7 +159,7 @@ describe('should do basic subscription to an async state', () => {
await jest.advanceTimersByTime(10);
});
- expect(screen.getByTestId("status").innerHTML).toEqual(AsyncStateStatus.success);
+ expect(screen.getByTestId("status").innerHTML).toEqual(Status.success);
expect(screen.getByTestId("result").innerHTML).toEqual("1");
expect(screen.getByTestId("pending").innerHTML).toEqual("");
});
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/outside-provider/source.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/outside-provider/source.test.tsx
index 7aabb906..96374e37 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/outside-provider/source.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/outside-provider/source.test.tsx
@@ -1,5 +1,5 @@
import * as React from "react";
-import {fireEvent, render, screen} from "@testing-library/react";
+import {fireEvent, render, screen, act} from "@testing-library/react";
import {UseAsyncState} from "../../../../../types.internal";
import {useAsyncState} from "../../../../../react/useAsyncState";
import AsyncStateComponent from "../../../utils/AsyncStateComponent";
@@ -61,7 +61,10 @@ describe('should subscribe to a module level source object', () => {
expect(screen.getByTestId("count-a").innerHTML).toEqual("0");
expect(screen.getByTestId("count-b").innerHTML).toEqual("0");
- fireEvent.click(incrementBtn);
+
+ act(() => {
+ fireEvent.click(incrementBtn);
+ });
expect(screen.getByTestId("count-a").innerHTML).toEqual("1");
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/post-subscribe/index.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/post-subscribe/index.test.tsx
index a81bd639..5e5c5049 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/post-subscribe/index.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/post-subscribe/index.test.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
import {act, fireEvent, render, screen} from "@testing-library/react";
import AsyncStateComponent from "../../../utils/AsyncStateComponent";
import {UseAsyncState} from "../../../../../types.internal";
-import {AsyncStateStatus, createSource} from "../../../../../async-state";
+import {Status, createSource} from "../../../../../async-state";
import {mockDateNow, TESTS_TS} from "../../../utils/setup";
// @ts-ignore
@@ -81,7 +81,7 @@ describe('should post subscribe', () => {
)
expect(mocked).toHaveBeenCalledTimes(2); // 1 strict mode
expect(mocked).toHaveBeenCalledWith({
- status: AsyncStateStatus.initial,
+ status: Status.initial,
timestamp: TESTS_TS,
props: null,
data: 0
@@ -103,7 +103,7 @@ describe('should post subscribe', () => {
await jest.advanceTimersByTime(9);
});
- expect(screen.getByTestId("status").innerHTML).toEqual(AsyncStateStatus.pending);
+ expect(screen.getByTestId("status").innerHTML).toEqual(Status.pending);
onAbort.mockClear();
act(() => {
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/standalone/index.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/standalone/index.test.tsx
index c613d952..4b19d86f 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/standalone/index.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useAsyncState/subscription/standalone/index.test.tsx
@@ -1,11 +1,10 @@
import * as React from "react";
import {fireEvent, render, screen} from "@testing-library/react";
import {
- SubscriptionMode,
UseAsyncState
} from "../../../../../types.internal";
import {useAsyncState} from "../../../../../react/useAsyncState";
-import {AsyncStateProvider} from "../../../../../react/AsyncStateProvider";
+import {AsyncStateProvider} from "../../../../../react/Provider";
describe('should declare a standalone producer inside a provider', () => {
it('should declare a standalone producer inside a provider ', async () => {
@@ -21,7 +20,7 @@ describe('should declare a standalone producer inside a provider', () => {
function Component() {
const {
run,
- mode,
+ devFlags,
state,
}: UseAsyncState
= useAsyncState({
selector: d => d.data,
@@ -43,7 +42,7 @@ describe('should declare a standalone producer inside a provider', () => {
increment
decrement
- {mode}
+ {JSON.stringify(devFlags)}
{state}
);
}
@@ -60,7 +59,7 @@ describe('should declare a standalone producer inside a provider', () => {
const decrementBtn = screen.getByTestId("decrement");
// then
expect(screen.getByTestId("mode").innerHTML)
- .toEqual(SubscriptionMode.ALONE);
+ .toEqual("[\"CONFIG_OBJECT\",\"INSIDE_PROVIDER\",\"SELECTOR\",\"STANDALONE\"]");
// +1
fireEvent.click(incrementBtn);
@@ -82,7 +81,7 @@ describe('should declare a standalone producer inside a provider', () => {
function Component() {
const {
- mode,
+ devFlags,
}: UseAsyncState = useAsyncState({
key: "standalone",
producer(props) {
@@ -91,7 +90,7 @@ describe('should declare a standalone producer inside a provider', () => {
initialValue: 0,
selector: d => d.data,
});
- return {mode} ;
+ return {JSON.stringify(devFlags)} ;
}
// when
@@ -104,6 +103,6 @@ describe('should declare a standalone producer inside a provider', () => {
// then
expect(screen.getByTestId("mode").innerHTML)
- .toEqual(SubscriptionMode.ALONE);
+ .toEqual("[\"CONFIG_OBJECT\",\"INSIDE_PROVIDER\",\"SELECTOR\",\"WAIT\"]");
});
});
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useProducer/index.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useProducer/index.test.tsx
index caf8af9d..f50aa13b 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useProducer/index.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useProducer/index.test.tsx
@@ -1,8 +1,7 @@
import * as React from "react";
import {fireEvent, render, screen} from "@testing-library/react";
-import {AsyncStateProvider} from "../../../react/AsyncStateProvider";
-import {SubscriptionMode} from "../../../types.internal";
-import {useProducer} from "../../../react/useAsyncStateBase";
+import {AsyncStateProvider} from "../../../react/Provider";
+import {useProducer} from "../../../react/useAsyncState";
describe('should useProducer', () => {
it('should use a global producer ', async () => {
@@ -20,8 +19,8 @@ describe('should useProducer', () => {
function Component() {
const {
run,
- mode,
state,
+ devFlags,
} = useProducer(producer);
function increment() {
@@ -31,7 +30,7 @@ describe('should useProducer', () => {
return (
increment
- {mode}
+ {JSON.stringify(devFlags)}
{state.data}
);
}
@@ -48,7 +47,7 @@ describe('should useProducer', () => {
// then
expect(screen.getByTestId("result").innerHTML).toEqual("");
expect(screen.getByTestId("mode").innerHTML)
- .toEqual(SubscriptionMode.ALONE);
+ .toEqual("[\"CONFIG_FUNCTION\",\"STANDALONE\"]");
// +1
fireEvent.click(incrementBtn);
@@ -73,7 +72,7 @@ describe('should useProducer', () => {
function Component() {
const {
run,
- mode,
+ devFlags,
state,
} = useProducer(producer);
@@ -84,7 +83,7 @@ describe('should useProducer', () => {
return (
increment
- {mode}
+ {JSON.stringify(devFlags)}
{state.data}
);
}
@@ -101,7 +100,7 @@ describe('should useProducer', () => {
// then
expect(screen.getByTestId("result").innerHTML).toEqual("");
expect(screen.getByTestId("mode").innerHTML)
- .toEqual(SubscriptionMode.ALONE);
+ .toEqual("[\"CONFIG_FUNCTION\",\"INSIDE_PROVIDER\",\"STANDALONE\"]");
// +1
fireEvent.click(incrementBtn);
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useSelector/index.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useSelector/index.test.tsx
index 049c3d38..6c6a02b2 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useSelector/index.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useSelector/index.test.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import {act, fireEvent, render, screen} from "@testing-library/react";
-import {AsyncStateProvider} from "../../../react/AsyncStateProvider";
+import {AsyncStateProvider} from "../../../react/Provider";
import {useSelector} from "../../../react/useSelector";
import {useRun} from "../../../react/useRun";
import {createSource} from "../../../async-state";
diff --git a/packages/react-async-states/src/__tests__/react-async-state/useSource/index.test.tsx b/packages/react-async-states/src/__tests__/react-async-state/useSource/index.test.tsx
index 9396387a..7b82990d 100644
--- a/packages/react-async-states/src/__tests__/react-async-state/useSource/index.test.tsx
+++ b/packages/react-async-states/src/__tests__/react-async-state/useSource/index.test.tsx
@@ -1,7 +1,6 @@
import * as React from "react";
import {act, render, screen} from "@testing-library/react";
-import {useSource} from "../../../react/useAsyncStateBase";
-import {SubscriptionMode} from "../../../types.internal";
+import {useSource} from "../../../react/useAsyncState";
import {createSource} from "../../../async-state";
describe('should useSource', () => {
@@ -18,13 +17,13 @@ describe('should useSource', () => {
function Component() {
const {
run,
- mode,
+ devFlags,
state,
} = useSource(source);
return (
- {mode}
+ {JSON.stringify(devFlags)}
{state.data}
);
}
@@ -40,7 +39,7 @@ describe('should useSource', () => {
// then
expect(screen.getByTestId("result").innerHTML).toEqual("8");
expect(screen.getByTestId("mode").innerHTML)
- .toEqual(SubscriptionMode.SRC);
+ .toEqual("[\"CONFIG_SOURCE\",\"SOURCE\"]");
act(() => {
source.setState(5);
diff --git a/packages/react-async-states/src/__tests__/v2/get-flags.test.ts b/packages/react-async-states/src/__tests__/v2/get-flags.test.ts
new file mode 100644
index 00000000..214998b8
--- /dev/null
+++ b/packages/react-async-states/src/__tests__/v2/get-flags.test.ts
@@ -0,0 +1,276 @@
+import {
+ AUTO_RUN,
+ CONFIG_FUNCTION,
+ CONFIG_OBJECT,
+ CONFIG_SOURCE,
+ CONFIG_STRING,
+ FORK,
+ HOIST,
+ INSIDE_PROVIDER,
+ LANE,
+ SOURCE,
+ STANDALONE,
+ WAIT
+} from "../../react/StateHookFlags";
+import {AsyncStateManager, createSource} from "../../async-state";
+import {resolveFlags} from "../../react/StateHook";
+
+describe('resolveFlags', () => {
+ describe('get flags from config outside provider', () => {
+ it('should correctly infer configuration from key: -- string --', () => {
+ expect(resolveFlags("key", null))
+ .toEqual(CONFIG_STRING | STANDALONE);
+
+ expect(resolveFlags("key", null, {
+ lazy: false,
+ hoist: true
+ }))
+ .toEqual(CONFIG_STRING | AUTO_RUN | HOIST);
+
+ expect(resolveFlags("key", null, {fork: true}))
+ .toEqual(CONFIG_STRING | STANDALONE | FORK);
+
+ expect(resolveFlags("key", null, {lane: "lane"}))
+ .toEqual(CONFIG_STRING | STANDALONE | LANE);
+ });
+ it('should correctly infer configuration from key: -- object with key --', () => {
+ let key = "key"
+
+ expect(resolveFlags({key}, null))
+ .toEqual(CONFIG_OBJECT | STANDALONE);
+
+ expect(resolveFlags({key, lazy: false, hoist: true}, null))
+ .toEqual(CONFIG_OBJECT | AUTO_RUN | HOIST);
+
+ expect(resolveFlags({key}, null, {fork: true}))
+ .toEqual(CONFIG_OBJECT | STANDALONE | FORK);
+
+ expect(resolveFlags({key, lane: "lane"}, null))
+ .toEqual(CONFIG_OBJECT | STANDALONE | LANE);
+
+ expect(resolveFlags({key, producer: () => 5}, null, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | STANDALONE | AUTO_RUN);
+ });
+ it('should correctly infer configuration from source: -- source --', () => {
+ let source = createSource("tmp");
+
+ expect(resolveFlags(source, null))
+ .toEqual(CONFIG_SOURCE | SOURCE);
+
+ expect(resolveFlags(source, null, {lazy: false}))
+ .toEqual(CONFIG_SOURCE | SOURCE | AUTO_RUN);
+
+ expect(resolveFlags(source, null, {fork: true}))
+ .toEqual(CONFIG_SOURCE | SOURCE | FORK);
+
+ expect(resolveFlags(source, null, {lane: "lane"}))
+ .toEqual(CONFIG_SOURCE | SOURCE | LANE);
+
+ expect(resolveFlags(source, null, {producer: () => 5, lazy: false}))
+ .toEqual(CONFIG_SOURCE | SOURCE | AUTO_RUN);
+ });
+ it('should correctly infer configuration from source: -- object with source --', () => {
+ let source = createSource("tmp");
+
+ expect(resolveFlags({source}, null))
+ .toEqual(CONFIG_OBJECT | SOURCE);
+
+ expect(resolveFlags({source}, null, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | SOURCE | AUTO_RUN);
+
+ expect(resolveFlags({source}, null, {fork: true}))
+ .toEqual(CONFIG_OBJECT | SOURCE | FORK);
+
+ expect(resolveFlags({source}, null, {lane: "lane"}))
+ .toEqual(CONFIG_OBJECT | SOURCE | LANE);
+
+ expect(resolveFlags({source, producer: () => 5, lazy: false}, null))
+ .toEqual(CONFIG_OBJECT | SOURCE | AUTO_RUN);
+ });
+ it('should correctly infer configuration from producer: -- producer --', () => {
+ let producer = () => 5;
+
+ expect(resolveFlags(producer, null))
+ .toEqual(CONFIG_FUNCTION | STANDALONE);
+
+ expect(resolveFlags(producer, null, {lazy: false}))
+ .toEqual(CONFIG_FUNCTION | STANDALONE | AUTO_RUN);
+
+ expect(resolveFlags(producer, null, {fork: true}))
+ .toEqual(CONFIG_FUNCTION | STANDALONE | FORK);
+
+ expect(resolveFlags(producer, null, {lane: "lane"}))
+ .toEqual(CONFIG_FUNCTION | STANDALONE | LANE);
+ });
+ it('should correctly infer configuration from producer: -- object with producer --', () => {
+ let producer = () => 5;
+
+ expect(resolveFlags({producer}, null))
+ .toEqual(CONFIG_OBJECT | STANDALONE);
+
+ expect(resolveFlags({producer}, null, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | STANDALONE | AUTO_RUN);
+
+ expect(resolveFlags({producer}, null, {fork: true}))
+ .toEqual(CONFIG_OBJECT | STANDALONE | FORK);
+
+ expect(resolveFlags({producer}, null, {lane: "lane"}))
+ .toEqual(CONFIG_OBJECT | STANDALONE | LANE);
+ });
+ it('should correctly infer configuration from object: -- remaining cases --', () => {
+
+ expect(resolveFlags({
+ key: "test",
+ payload: {},
+ lazy: false,
+ producer: () => 5,
+ }, null))
+ .toEqual(CONFIG_OBJECT | AUTO_RUN | STANDALONE);
+
+ });
+ });
+
+ describe('get flags from config inside provider', () => {
+ it('should correctly infer configuration from key: -- string --', () => {
+ let manager = AsyncStateManager({key: {key: "key"}});
+
+ expect(resolveFlags("key", manager))
+ .toEqual(CONFIG_STRING | INSIDE_PROVIDER);
+
+ expect(resolveFlags("not-existing", manager))
+ .toEqual(CONFIG_STRING | INSIDE_PROVIDER | WAIT);
+
+ expect(resolveFlags("key", manager, {
+ lazy: false,
+ hoist: true
+ }))
+ .toEqual(CONFIG_STRING | INSIDE_PROVIDER | HOIST | AUTO_RUN);
+
+ expect(resolveFlags("key", manager, {fork: true}))
+ .toEqual(CONFIG_STRING | INSIDE_PROVIDER | FORK);
+
+ expect(resolveFlags("key", manager, {lane: "lane"}))
+ .toEqual(CONFIG_STRING | INSIDE_PROVIDER | LANE);
+ });
+ it('should correctly infer configuration from key: -- object with key --', () => {
+ let key = "key";
+ let manager = AsyncStateManager({key: {key}});
+
+ expect(resolveFlags({key}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER);
+ expect(resolveFlags({key: "not-existing", lazy: false}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | WAIT | AUTO_RUN);
+
+ expect(resolveFlags({key, lazy: false, hoist: true}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | AUTO_RUN | HOIST);
+
+ expect(resolveFlags({key}, manager, {fork: true}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | FORK);
+
+ expect(resolveFlags({key, lane: "lane"}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | LANE);
+
+ expect(resolveFlags({key, producer: () => 5}, manager, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | AUTO_RUN);
+ });
+ it('should correctly infer configuration from source: -- source --', () => {
+ let source = createSource("tmp");
+ let manager = AsyncStateManager({key: source});
+
+ expect(resolveFlags(source, manager))
+ .toEqual(CONFIG_SOURCE | INSIDE_PROVIDER | SOURCE);
+
+ expect(resolveFlags(source, manager, {lazy: false}))
+ .toEqual(CONFIG_SOURCE | INSIDE_PROVIDER | SOURCE | AUTO_RUN);
+
+ expect(resolveFlags(source, manager, {fork: true}))
+ .toEqual(CONFIG_SOURCE | INSIDE_PROVIDER | SOURCE | FORK);
+
+ expect(resolveFlags(source, manager, {lane: "lane"}))
+ .toEqual(CONFIG_SOURCE | INSIDE_PROVIDER | SOURCE | LANE);
+
+ expect(resolveFlags(source, manager, {producer: () => 5, lazy: false}))
+ .toEqual(CONFIG_SOURCE | INSIDE_PROVIDER | SOURCE | AUTO_RUN);
+ });
+ it('should correctly infer configuration from source: -- object with source --', () => {
+ let source = createSource("tmp");
+ let manager = AsyncStateManager({key: source});
+
+ expect(resolveFlags({source}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | SOURCE);
+
+ expect(resolveFlags({source}, manager, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | SOURCE | AUTO_RUN);
+
+ expect(resolveFlags({source}, manager, {fork: true}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | SOURCE | FORK);
+
+ expect(resolveFlags({source}, manager, {lane: "lane"}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | SOURCE | LANE);
+
+ expect(resolveFlags({source, producer: () => 5, lazy: false}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | SOURCE | AUTO_RUN);
+ });
+ it('should correctly infer configuration from producer: -- producer --', () => {
+ let producer = () => 5;
+ let manager = AsyncStateManager({key: {key: "key", producer}});
+
+ expect(resolveFlags(producer, manager))
+ .toEqual(CONFIG_FUNCTION | INSIDE_PROVIDER | STANDALONE);
+
+ expect(resolveFlags(producer, manager, {lazy: false}))
+ .toEqual(CONFIG_FUNCTION | INSIDE_PROVIDER | STANDALONE | AUTO_RUN);
+
+ expect(resolveFlags(producer, manager, {fork: true}))
+ .toEqual(CONFIG_FUNCTION | INSIDE_PROVIDER | STANDALONE | FORK);
+
+ expect(resolveFlags(producer, manager, {lane: "lane"}))
+ .toEqual(CONFIG_FUNCTION | INSIDE_PROVIDER | STANDALONE | LANE);
+ });
+ it('should correctly infer configuration from producer: -- object with producer --', () => {
+ let producer = () => 5;
+ let manager = AsyncStateManager({key: {key: "key", producer}});
+
+ expect(resolveFlags({producer}, manager))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | STANDALONE);
+
+ expect(resolveFlags({producer}, manager, {lazy: false}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | STANDALONE | AUTO_RUN);
+
+ expect(resolveFlags({producer}, manager, {fork: true}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | STANDALONE | FORK);
+
+ expect(resolveFlags({producer}, manager, {lane: "lane"}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | STANDALONE | LANE);
+
+ // listen to the existing!
+ expect(resolveFlags({key: "key", producer}, manager, {hoist: true}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | HOIST);
+
+ expect(resolveFlags({key: "key2", producer}, manager, {hoist: true}))
+ .toEqual(CONFIG_OBJECT | INSIDE_PROVIDER | HOIST);
+ });
+ it('should correctly infer configuration from object: -- remaining cases --', () => {
+
+ let manager = AsyncStateManager({key: {key: "key"}});
+
+ expect(resolveFlags({
+ key: "test",
+ payload: {},
+ lazy: false,
+ producer: () => 5,
+ }, manager))
+ .toEqual(CONFIG_OBJECT | AUTO_RUN | INSIDE_PROVIDER | WAIT);
+
+ expect(resolveFlags({
+ key: "key",
+ payload: {},
+ lazy: false,
+ producer: () => 5,
+ }, manager))
+ .toEqual(CONFIG_OBJECT | AUTO_RUN | INSIDE_PROVIDER);
+
+ });
+ });
+
+});
diff --git a/packages/react-async-states/src/__tests__/v2/resolve-instance.test.ts b/packages/react-async-states/src/__tests__/v2/resolve-instance.test.ts
new file mode 100644
index 00000000..672a2a8e
--- /dev/null
+++ b/packages/react-async-states/src/__tests__/v2/resolve-instance.test.ts
@@ -0,0 +1,162 @@
+import {createStateHook} from "../../react/useAsyncState";
+import {
+ CONFIG_OBJECT,
+ CONFIG_SOURCE, CONFIG_STRING, FORK, HOIST,
+ INSIDE_PROVIDER, LANE,
+ SOURCE, STANDALONE,
+ WAIT
+} from "../../react/StateHookFlags";
+import AsyncState, {AsyncStateManager} from "../../async-state";
+import {resolveInstance, StateHook} from "../../react/StateHook";
+
+describe('resolveInstance', () => {
+ it('should resolve instance in WAIT mode', () => {
+ expect(resolveInstance(WAIT, "irrelevant", null, null)).toBe(null);
+ expect(resolveInstance(WAIT | INSIDE_PROVIDER, {}, null, null)).toBe(null);
+ });
+ it('should resolve instance Sources', () => {
+ let instance = new AsyncState("key", null);
+
+ expect(resolveInstance(CONFIG_SOURCE | SOURCE, instance._source, null, null))
+ .toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | SOURCE | INSIDE_PROVIDER,
+ {source: instance._source},
+ null,
+ null)
+ ).toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | SOURCE | INSIDE_PROVIDER | LANE,
+ {source: instance._source, lane: 'test'},
+ null,
+ null)
+ ).toBe(instance.getLane('test'));
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | SOURCE | INSIDE_PROVIDER | FORK,
+ {source: instance._source, fork: true},
+ null,
+ null)
+ ).not.toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_SOURCE | SOURCE | INSIDE_PROVIDER | FORK,
+ instance._source,
+ null,
+ null)
+ ).not.toBe(instance);
+ });
+ it('should resolve instance when inside provider', () => {
+ let instance = new AsyncState("key", null);
+ let manager = AsyncStateManager({ key: instance._source });
+
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | INSIDE_PROVIDER | HOIST,
+ {key: "key", hoistConfig: {override: true}},
+ manager,
+ null)
+ ).not.toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_STRING | INSIDE_PROVIDER | HOIST,
+ "key",
+ manager,
+ null)
+ ).toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | INSIDE_PROVIDER | HOIST,
+ {key: "key"},
+ manager,
+ null)
+ ).toBe(instance);
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | INSIDE_PROVIDER | HOIST | LANE,
+ {key: "key", lane: "test"},
+ manager,
+ null)
+ ).toBe(instance.getLane("test"));
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | INSIDE_PROVIDER | HOIST | FORK | LANE,
+ {key: "key", lane: "test"},
+ manager,
+ null)
+ ).not.toBe(instance.getLane("test"));
+
+ expect(
+ resolveInstance(
+ CONFIG_STRING | INSIDE_PROVIDER,
+ "key",
+ manager,
+ null)
+ ).toBe(instance);
+ });
+ it('should resolve instance when standalone', () => {
+ let instance = new AsyncState("key", null);
+ let manager = AsyncStateManager({ key: instance._source });
+
+ expect(
+ resolveInstance(
+ CONFIG_OBJECT | INSIDE_PROVIDER | STANDALONE,
+ {initialValue: 5},
+ manager,
+ null).config.initialValue
+ ).toBe(5);
+
+
+ expect(
+ resolveInstance(
+ STANDALONE,
+ undefined,
+ null,
+ null).key.startsWith("async-state-")
+ ).toBe(true);
+
+ let hook: StateHook = createStateHook();
+
+ hook.flags = CONFIG_STRING | STANDALONE;
+ hook.instance = instance;
+ hook.config = "key";
+
+
+ // reused instance with patches
+ let newInstance = resolveInstance(
+ CONFIG_OBJECT | STANDALONE,
+ {key: "key", initialValue: 15},
+ null,
+ hook);
+
+ expect(newInstance).toBe(instance);
+ expect(newInstance.config.initialValue).toBe(15);
+ expect(newInstance.originalProducer).toBe(undefined);
+
+ // dont reuse becaue of flags
+
+ hook.flags = CONFIG_STRING | WAIT;
+ hook.config = "key";
+
+ newInstance = resolveInstance(
+ CONFIG_OBJECT | STANDALONE,
+ {key: "key", initialValue: 15},
+ null,
+ hook);
+
+ expect(newInstance).not.toBe(instance);
+ expect(newInstance.config.initialValue).toBe(15);
+ expect(newInstance.originalProducer).toBe(undefined);
+ });
+});
diff --git a/packages/react-async-states/src/async-state/AsyncState.ts b/packages/react-async-states/src/async-state/AsyncState.ts
index 3d8ca378..051adfdf 100644
--- a/packages/react-async-states/src/async-state/AsyncState.ts
+++ b/packages/react-async-states/src/async-state/AsyncState.ts
@@ -3,7 +3,7 @@ import {
asyncStatesKey,
didNotExpire,
hash,
- isAsyncStateSource,
+ isSource,
sourceIsSourceSymbol,
} from "./utils";
import devtools from "../devtools/Devtools";
@@ -34,7 +34,6 @@ class AsyncState implements StateInterface {
subsIndex: number = 0;
subscriptions: Record> | null = null;
- producer: ProducerFunction;
suspender: Promise | undefined = undefined;
originalProducer: Producer | undefined;
@@ -57,7 +56,6 @@ class AsyncState implements StateInterface {
this.key = key;
this.uniqueId = nextUniqueId();
this.config = shallowClone(config);
- this.producer = wrapProducerFunction(this);
this.originalProducer = producer ?? undefined;
this.producerType = producer ? ProducerType.indeterminate : ProducerType.notProvided;
@@ -79,6 +77,7 @@ class AsyncState implements StateInterface {
this.abort = this.abort.bind(this);
this.getState = this.getState.bind(this);
this.setState = this.setState.bind(this);
+ this.producer = this.producer.bind(this);
this.subscribe = this.subscribe.bind(this);
this.getPayload = this.getPayload.bind(this);
this.mergePayload = this.mergePayload.bind(this);
@@ -109,10 +108,119 @@ class AsyncState implements StateInterface {
return this.config;
}
- patchConfig(partialConfig: Partial>) {
+ patchConfig(partialConfig?: Partial>) {
Object.assign(this.config, partialConfig);
}
+ producer(
+ props: ProducerProps,
+ indicators: RunIndicators,
+ callbacks?: ProducerCallbacks,
+ ): AbortFn {
+ let instance = this;
+ const currentProducer = this.originalProducer;
+ if (typeof currentProducer !== "function") {
+ indicators.fulfilled = true;
+ instance.producerType = ProducerType.notProvided;
+ instance.setState(props.args[0], props.args[1]);
+ if (callbacks) {
+ switch (instance.state.status) {
+ case Status.success: {
+ callbacks.onSuccess?.(instance.state);
+ break;
+ }
+ case Status.aborted: {
+ callbacks.onAborted?.(instance.state);
+ break;
+ }
+ case Status.error: {
+ callbacks.onError?.(instance.state);
+ break;
+ }
+ }
+ }
+ return;
+ }
+ // the running promise is used to pass the status to pending and as suspender in react18+
+ let runningPromise;
+ // the execution value is the return of the initial producer function
+ let executionValue;
+ // it is important to clone to capture properties and save only serializable stuff
+ const savedProps = cloneProducerProps(props);
+
+ try {
+ executionValue = currentProducer(props);
+ } catch (e) {
+ if (__DEV__) devtools.emitRunSync(instance, savedProps);
+ indicators.fulfilled = true;
+ let errorState = StateBuilder.error(e, savedProps);
+ instance.replaceState(errorState);
+ callbacks?.onError?.(errorState);
+ return;
+ }
+
+ if (isGenerator(executionValue)) {
+ instance.producerType = ProducerType.generator;
+ if (__DEV__) devtools.emitRunGenerator(instance, savedProps);
+ // generatorResult is either {done, value} or a promise
+ let generatorResult;
+ try {
+ generatorResult = wrapStartedGenerator(executionValue, props, indicators);
+ } catch (e) {
+ indicators.fulfilled = true;
+ let errorState = StateBuilder.error(e, savedProps);
+ instance.replaceState(errorState);
+ callbacks?.onError?.(errorState);
+ return;
+ }
+ if (generatorResult.done) {
+ indicators.fulfilled = true;
+ let successState = StateBuilder.success(generatorResult.value, savedProps);
+ instance.replaceState(successState);
+ callbacks?.onSuccess?.(successState);
+ return;
+ } else {
+ runningPromise = generatorResult;
+ instance.suspender = runningPromise;
+ instance.replaceState(StateBuilder.pending(savedProps) as State);
+ }
+ } else if (isPromise(executionValue)) {
+ instance.producerType = ProducerType.promise;
+ if (__DEV__) devtools.emitRunPromise(instance, savedProps);
+ runningPromise = executionValue;
+ instance.suspender = runningPromise;
+ instance.replaceState(StateBuilder.pending(savedProps) as State);
+ } else { // final value
+ if (__DEV__) devtools.emitRunSync(instance, savedProps);
+ indicators.fulfilled = true;
+ instance.producerType = ProducerType.sync;
+ let successState = StateBuilder.success(executionValue, savedProps);
+ instance.replaceState(successState);
+ callbacks?.onSuccess?.(successState);
+ return;
+ }
+
+ runningPromise
+ .then(stateData => {
+ let aborted = indicators.aborted;
+ if (!aborted) {
+ indicators.fulfilled = true;
+ let successState = StateBuilder.success(stateData, savedProps);
+ instance.replaceState(successState);
+ callbacks?.onSuccess?.(successState);
+ }
+ })
+ .catch(stateError => {
+ let aborted = indicators.aborted;
+ if (!aborted) {
+ indicators.fulfilled = true;
+ let errorState = StateBuilder.error(stateError, savedProps);
+ instance.replaceState(errorState);
+ callbacks?.onError?.(errorState);
+ }
+ });
+ };
+
getPayload(): Record {
if (!this.payload) {
this.payload = {};
@@ -154,7 +262,7 @@ class AsyncState implements StateInterface {
notify: boolean = true
): void {
- if (newState.status === AsyncStateStatus.pending && this.config.skipPendingStatus) {
+ if (newState.status === Status.pending && this.config.skipPendingStatus) {
return;
}
@@ -166,7 +274,7 @@ class AsyncState implements StateInterface {
this.pendingUpdate = null;
}
- if (newState.status === AsyncStateStatus.pending) {
+ if (newState.status === Status.pending) {
if (
areRunEffectsSupported()
&& this.config.skipPendingDelayMs
@@ -182,14 +290,14 @@ class AsyncState implements StateInterface {
this.version += 1;
if (__DEV__) devtools.emitUpdate(this);
- if (this.state.status === AsyncStateStatus.success) {
+ if (this.state.status === Status.success) {
this.lastSuccess = this.state;
if (isCacheEnabled(this)) {
saveCacheAfterSuccessfulUpdate(this);
}
}
- if (this.state.status !== AsyncStateStatus.pending) {
+ if (this.state.status !== Status.pending) {
this.suspender = undefined;
}
@@ -199,8 +307,7 @@ class AsyncState implements StateInterface {
}
subscribe(
- cb,
- subKey?: string | undefined
+ props: AsyncStateSubscribeProps
): AbortFn {
if (!this.subscriptions) {
this.subscriptions = {};
@@ -208,10 +315,11 @@ class AsyncState implements StateInterface {
let that = this;
this.subsIndex += 1;
- let subscriptionKey: string | undefined = subKey;
- if (subKey === undefined) {
- subscriptionKey = `subscription-$${this.subsIndex}`;
+ let subscriptionKey: string | undefined = props.key;
+
+ if (subscriptionKey === undefined) {
+ subscriptionKey = `$${this.subsIndex}`;
}
function cleanup() {
@@ -225,26 +333,22 @@ class AsyncState implements StateInterface {
}
}
- this.subscriptions[subscriptionKey!] = {
- cleanup,
- callback: cb,
- key: subscriptionKey,
- };
+ this.subscriptions[subscriptionKey] = {props, cleanup};
this.locks += 1;
- if (__DEV__) devtools.emitSubscription(this, subscriptionKey!);
+ if (__DEV__) devtools.emitSubscription(this, subscriptionKey);
return cleanup;
}
setState(
newValue: T | StateFunctionUpdater,
- status = AsyncStateStatus.success,
+ status = Status.success,
): void {
if (!StateBuilder[status]) {
throw new Error(`Couldn't replace state to unknown status ${status}.`);
}
this.willUpdate = true;
- if (this.state?.status === AsyncStateStatus.pending) {
+ if (this.state?.status === Status.pending) {
this.abort();
this.currentAbort = undefined;
}
@@ -386,10 +490,10 @@ class AsyncState implements StateInterface {
const now = Date.now();
switch (this.config.runEffect) {
- case ProducerRunEffects.delay:
- case ProducerRunEffects.debounce:
- case ProducerRunEffects.takeLast:
- case ProducerRunEffects.takeLatest: {
+ case RunEffect.delay:
+ case RunEffect.debounce:
+ case RunEffect.takeLast:
+ case RunEffect.takeLatest: {
if (this.pendingTimeout) {
const deadline = this.pendingTimeout.startDate + effectDurationMs;
if (now < deadline) {
@@ -398,9 +502,9 @@ class AsyncState implements StateInterface {
}
return scheduleDelayedRun(now);
}
- case ProducerRunEffects.throttle:
- case ProducerRunEffects.takeFirst:
- case ProducerRunEffects.takeLeading: {
+ case RunEffect.throttle:
+ case RunEffect.takeFirst:
+ case RunEffect.takeLeading: {
if (this.pendingTimeout) {
const deadline = this.pendingTimeout.startDate + effectDurationMs;
if (now <= deadline) {
@@ -431,7 +535,7 @@ class AsyncState implements StateInterface {
): AbortFn {
this.willUpdate = true;
- if (this.state.status === AsyncStateStatus.pending || this.pendingUpdate) {
+ if (this.state.status === Status.pending || this.pendingUpdate) {
if (this.pendingUpdate) {
clearTimeout(this.pendingUpdate.timeoutId);
// this.pendingUpdate.callback(); skip the callback!
@@ -619,9 +723,9 @@ function constructPropsObject(
function emit(
updater: T | StateFunctionUpdater,
- status?: AsyncStateStatus
+ status?: Status
): void {
- if (runIndicators.cleared && instance.state.status === AsyncStateStatus.aborted) {
+ if (runIndicators.cleared && instance.state.status === Status.aborted) {
console.error("You are emitting while your producer is passing to aborted state." +
"This has no effect and not supported by the library. The next " +
"state value on aborted state is the reason of the abort.");
@@ -687,7 +791,7 @@ function nextUniqueId() {
return ++uniqueId;
}
-function readInstanceFromSource(possiblySource: Source): StateInterface {
+function readSource(possiblySource: Source): StateInterface {
try {
const candidate = possiblySource.constructor(asyncStatesKey);
if (!(candidate instanceof AsyncState)) {
@@ -804,7 +908,7 @@ function notifySubscribers(instance: StateInterface) {
return;
}
Object.values(instance.subscriptions).forEach(subscription => {
- subscription.callback(instance.state);
+ subscription.props.cb(instance.state);
});
}
@@ -880,7 +984,7 @@ function isCacheEnabled(instance: StateInterface): boolean {
function state(
- status: AsyncStateStatus,
+ status: Status,
data: T | any,
props: ProducerSavedProps | null
): State {
@@ -888,20 +992,20 @@ function state(
}
export const StateBuilder = Object.freeze({
- initial: (initialValue) => state(AsyncStateStatus.initial, initialValue, null),
+ initial: (initialValue) => state(Status.initial, initialValue, null),
error: (
data,
props
- ) => state(AsyncStateStatus.error, data, props),
+ ) => state(Status.error, data, props),
success: (
data,
props
- ) => state(AsyncStateStatus.success, data, props),
- pending: props => state(AsyncStateStatus.pending, null, props),
+ ) => state(Status.success, data, props),
+ pending: props => state(Status.pending, null, props),
aborted: (
reason,
props
- ) => state(AsyncStateStatus.aborted, reason, props),
+ ) => state(Status.aborted, reason, props),
}) as StateBuilderInterface;
//endregion
@@ -912,8 +1016,8 @@ export function standaloneProducerRunEffectFunction(
config: ProducerRunConfig | null,
...args: any[]
) {
- if (isAsyncStateSource(input)) {
- let instance = readInstanceFromSource(input as Source)
+ if (isSource(input)) {
+ let instance = readSource(input as Source)
.getLane(config?.lane);
return instance.run(standaloneProducerEffectsCreator, ...args);
@@ -935,8 +1039,8 @@ export function standaloneProducerRunpEffectFunction(
...args: any[]
) {
- if (isAsyncStateSource(input)) {
- let instance = readInstanceFromSource(input as Source).getLane(config?.lane);
+ if (isSource(input)) {
+ let instance = readSource(input as Source).getLane(config?.lane);
return runWhileSubscribingToNextResolve(instance, props, args);
} else if (typeof input === "function") {
@@ -957,15 +1061,15 @@ export function runWhileSubscribingToNextResolve(
args
): Promise> {
return new Promise(resolve => {
- let unsubscribe = instance.subscribe(subscription);
+ let unsubscribe = instance.subscribe({cb: subscription});
props.onAbort(unsubscribe);
let abort = instance.run(standaloneProducerEffectsCreator, ...args);
props.onAbort(abort);
function subscription(newState: State) {
- if (newState.status === AsyncStateStatus.success
- || newState.status === AsyncStateStatus.error) {
+ if (newState.status === Status.success
+ || newState.status === Status.error) {
if (typeof unsubscribe === "function") {
unsubscribe();
}
@@ -979,7 +1083,7 @@ export function standaloneProducerSelectEffectFunction(
input: ProducerRunInput,
lane?: string,
) {
- if (isAsyncStateSource(input)) {
+ if (isSource(input)) {
return (input as Source).getLaneSource(lane).getState()
}
}
@@ -997,119 +1101,6 @@ function standaloneProducerEffectsCreator(props: ProducerProps): ProducerE
//region WRAP PRODUCER FUNCTION
-export function wrapProducerFunction(instance: StateInterface): ProducerFunction {
- // this is the real deal
- return function producerFuncImpl(
- props: ProducerProps,
- indicators: RunIndicators,
- callbacks?: ProducerCallbacks,
- ): undefined {
-
- // this allows the developer to omit the producer attribute.
- // and replaces state when there is no producer
- const currentProducer = instance.originalProducer;
- if (typeof currentProducer !== "function") {
- indicators.fulfilled = true;
- instance.producerType = ProducerType.notProvided;
- instance.setState(props.args[0], props.args[1]);
- if (callbacks) {
- switch (instance.state.status) {
- case AsyncStateStatus.success: {
- callbacks.onSuccess?.(instance.state);
- break;
- }
- case AsyncStateStatus.aborted: {
- callbacks.onAborted?.(instance.state);
- break;
- }
- case AsyncStateStatus.error: {
- callbacks.onError?.(instance.state);
- break;
- }
- }
- }
- return;
- }
- // the running promise is used to pass the status to pending and as suspender in react18+
- let runningPromise;
- // the execution value is the return of the initial producer function
- let executionValue;
- // it is important to clone to capture properties and save only serializable stuff
- const savedProps = cloneProducerProps(props);
-
- try {
- executionValue = currentProducer(props);
- } catch (e) {
- if (__DEV__) devtools.emitRunSync(instance, savedProps);
- indicators.fulfilled = true;
- let errorState = StateBuilder.error(e, savedProps);
- instance.replaceState(errorState);
- callbacks?.onError?.(errorState);
- return;
- }
-
- if (isGenerator(executionValue)) {
- instance.producerType = ProducerType.generator;
- if (__DEV__) devtools.emitRunGenerator(instance, savedProps);
- // generatorResult is either {done, value} or a promise
- let generatorResult;
- try {
- generatorResult = wrapStartedGenerator(executionValue, props, indicators);
- } catch (e) {
- indicators.fulfilled = true;
- let errorState = StateBuilder.error(e, savedProps);
- instance.replaceState(errorState);
- callbacks?.onError?.(errorState);
- return;
- }
- if (generatorResult.done) {
- indicators.fulfilled = true;
- let successState = StateBuilder.success(generatorResult.value, savedProps);
- instance.replaceState(successState);
- callbacks?.onSuccess?.(successState);
- return;
- } else {
- runningPromise = generatorResult;
- instance.suspender = runningPromise;
- instance.replaceState(StateBuilder.pending(savedProps) as State);
- }
- } else if (isPromise(executionValue)) {
- instance.producerType = ProducerType.promise;
- if (__DEV__) devtools.emitRunPromise(instance, savedProps);
- runningPromise = executionValue;
- instance.suspender = runningPromise;
- instance.replaceState(StateBuilder.pending(savedProps) as State);
- } else { // final value
- if (__DEV__) devtools.emitRunSync(instance, savedProps);
- indicators.fulfilled = true;
- instance.producerType = ProducerType.sync;
- let successState = StateBuilder.success(executionValue, savedProps);
- instance.replaceState(successState);
- callbacks?.onSuccess?.(successState);
- return;
- }
-
- runningPromise
- .then(stateData => {
- let aborted = indicators.aborted;
- if (!aborted) {
- indicators.fulfilled = true;
- let successState = StateBuilder.success(stateData, savedProps);
- instance.replaceState(successState);
- callbacks?.onSuccess?.(successState);
- }
- })
- .catch(stateError => {
- let aborted = indicators.aborted;
- if (!aborted) {
- indicators.fulfilled = true;
- let errorState = StateBuilder.error(stateError, savedProps);
- instance.replaceState(errorState);
- callbacks?.onError?.(errorState);
- }
- });
- };
-}
function wrapStartedGenerator(
generatorInstance,
@@ -1214,7 +1205,7 @@ function stepAsyncAndContinueStartedGenerator(
//region Exports
export default AsyncState;
export {
- readInstanceFromSource,
+ readSource,
standaloneProducerEffectsCreator,
};
//endregion
@@ -1235,11 +1226,11 @@ export interface BaseSource {
setState(
updater: StateFunctionUpdater | T,
- status?: AsyncStateStatus,
+ status?: Status,
): void;
// subscriptions
- subscribe(cb: Function, subscriptionKey?: string): AbortFn,
+ subscribe(AsyncStateSubscribeProps: AsyncStateSubscribeProps): AbortFn,
// producer
replay(): AbortFn,
@@ -1253,11 +1244,18 @@ export interface BaseSource {
replaceCache(cacheKey: string, cache: CachedState): void,
- patchConfig(partialConfig: Partial>),
+ patchConfig(partialConfig?: Partial>),
getConfig(): ProducerConfig,
}
+export type AsyncStateSubscribeProps = {
+ key?: string,
+ flags?: number,
+ origin?: number,
+ cb(s: State): void,
+}
+
export interface StateInterface extends BaseSource {
// identity
version: number,
@@ -1330,7 +1328,7 @@ export interface RUNCProps extends ProducerCallbacks {
args?: any[],
}
-export enum AsyncStateStatus {
+export enum Status {
error = "error",
pending = "pending",
success = "success",
@@ -1338,7 +1336,7 @@ export enum AsyncStateStatus {
initial = "initial",
}
-export enum ProducerRunEffects {
+export enum RunEffect {
delay = "delay",
debounce = "debounce",
takeLast = "takeLast",
@@ -1352,7 +1350,7 @@ export enum ProducerRunEffects {
export type State = {
data: T,
timestamp: number,
- status: AsyncStateStatus,
+ status: Status,
props?: ProducerSavedProps | null,
};
@@ -1413,7 +1411,7 @@ export type ProducerConfig = {
initialValue?: T | ((cache: Record>) => T),
cacheConfig?: CacheConfig,
runEffectDurationMs?: number,
- runEffect?: ProducerRunEffects,
+ runEffect?: RunEffect,
skipPendingDelayMs?: number,
resetStateOnDispose?: boolean,
@@ -1425,7 +1423,7 @@ export type StateFunctionUpdater = (updater: State) => T;
export type StateUpdater = (
updater: T | StateFunctionUpdater,
- status?: AsyncStateStatus
+ status?: Status
) => void;
export interface Source extends BaseSource {
@@ -1447,15 +1445,14 @@ export type RunTask = {
}
export type StateSubscription = {
- key: string, // subscription key
cleanup: () => void,
- callback: (newState: State) => void,
+ props: AsyncStateSubscribeProps
};
export type OnCacheLoadProps = {
cache: Record>,
setState(
- newValue: T | StateFunctionUpdater, status?: AsyncStateStatus): void
+ newValue: T | StateFunctionUpdater, status?: Status): void
}
export type CacheConfig = {
diff --git a/packages/react-async-states/src/async-state/AsyncStateManager.ts b/packages/react-async-states/src/async-state/AsyncStateManager.ts
index 2964be9a..4174a6e2 100644
--- a/packages/react-async-states/src/async-state/AsyncStateManager.ts
+++ b/packages/react-async-states/src/async-state/AsyncStateManager.ts
@@ -11,14 +11,14 @@ import AsyncState, {
Source,
State,
StateInterface,
- readInstanceFromSource,
+ readSource,
runWhileSubscribingToNextResolve,
standaloneProducerRunEffectFunction,
standaloneProducerRunpEffectFunction,
standaloneProducerSelectEffectFunction
} from "./AsyncState";
-import {isAsyncStateSource,} from "./utils";
+import {isSource,} from "./utils";
const listenersKey = Symbol();
@@ -41,7 +41,7 @@ export function AsyncStateManager(
let watchers: ManagerWatchers = Object.create(null);
// @ts-ignore
- // ts is yelling at producerEffectsCreator property which will be assigned
+ // ts is yelling at createEffects property which will be assigned
// in the next statement.
const output: AsyncStateManagerInterface = {
entries: asyncStateEntries,
@@ -54,7 +54,7 @@ export function AsyncStateManager(
watchAll,
getAllKeys,
notifyWatchers,
- setInitialStates,
+ setStates: setInitialStates,
getPayload(): Record {
return payload;
},
@@ -62,7 +62,7 @@ export function AsyncStateManager(
Object.assign(payload, partialPayload);
}
};
- output.producerEffectsCreator = createProducerEffectsCreator(output);
+ output.createEffects = createProducerEffectsCreator(output);
return output;
@@ -116,7 +116,7 @@ export function AsyncStateManager(
asyncState: StateInterface,
...args: any[]
): AbortFn {
- return asyncState.run(output.producerEffectsCreator, ...args);
+ return asyncState.run(output.createEffects, ...args);
}
function dispose(
@@ -239,7 +239,7 @@ export function AsyncStateManager(
function hoist(
key: string,
instance: StateInterface,
- hoistConfig?: HoistToProviderConfig
+ hoistConfig?: hoistConfig
): StateInterface {
const existing = get(key);
@@ -281,10 +281,10 @@ function createInitialAsyncStatesReducer(
result: AsyncStateEntries,
current: ExtendedInitialAsyncState
): AsyncStateEntries {
- if (isAsyncStateSource(current)) {
+ if (isSource(current)) {
const key = current.key;
const existingEntry = result[key];
- const asyncState = readInstanceFromSource(
+ const asyncState = readSource(
current as Source);
if (!existingEntry || asyncState !== existingEntry.instance) {
@@ -343,7 +343,7 @@ function managerProducerRunFunction(
if (config?.lane) {
instance = instance.getLane(config.lane);
}
- return instance.run(manager.producerEffectsCreator, ...args);
+ return instance.run(manager.createEffects, ...args);
}
return standaloneProducerRunEffectFunction(input, config, ...args);
}
@@ -389,7 +389,7 @@ function managerProducerSelectFunction(
//endregion
//region TYPES
-export type HoistToProviderConfig = {
+export type hoistConfig = {
override: boolean,
}
@@ -422,7 +422,7 @@ export type AsyncStateManagerInterface = {
get(key: string): StateInterface,
hoist(
key: string, instance: StateInterface,
- hoistConfig?: HoistToProviderConfig
+ hoistConfig?: hoistConfig
): StateInterface,
dispose(asyncState: StateInterface): boolean,
watch(
@@ -435,12 +435,12 @@ export type AsyncStateManagerInterface = {
): void,
getAllKeys(): string[],
watchAll(cb: ManagerWatchCallback),
- setInitialStates(initialStates?: InitialStates): AsyncStateEntry[],
+ setStates(initialStates?: InitialStates): AsyncStateEntry[],
getPayload(): Record,
mergePayload(partialPayload?: Record): void,
- producerEffectsCreator(props: ProducerProps): ProducerEffects,
+ createEffects(props: ProducerProps): ProducerEffects,
}
export type InitialStatesObject = { [id: string]: ExtendedInitialAsyncState };
diff --git a/packages/react-async-states/src/async-state/index.ts b/packages/react-async-states/src/async-state/index.ts
index 9fe24f91..fe49a9ba 100644
--- a/packages/react-async-states/src/async-state/index.ts
+++ b/packages/react-async-states/src/async-state/index.ts
@@ -6,8 +6,8 @@ export {
createSource,
StateBuilder,
ProducerType,
- AsyncStateStatus,
- ProducerRunEffects,
+ Status,
+ RunEffect,
} from "./AsyncState";
export type {
@@ -44,7 +44,7 @@ export type {
InitialAsyncState,
AsyncStateSelector,
InitialStatesObject,
- HoistToProviderConfig,
+ hoistConfig,
ExtendedInitialAsyncState,
AsyncStateManagerInterface,
diff --git a/packages/react-async-states/src/async-state/utils.ts b/packages/react-async-states/src/async-state/utils.ts
index 5af7198f..9ce82ed1 100644
--- a/packages/react-async-states/src/async-state/utils.ts
+++ b/packages/react-async-states/src/async-state/utils.ts
@@ -23,7 +23,7 @@ export function didNotExpire(cachedState: CachedState) {
export const sourceIsSourceSymbol: symbol = Symbol();
-export function isAsyncStateSource(possiblySource: any) {
+export function isSource(possiblySource: any) {
return possiblySource && possiblySource[sourceIsSourceSymbol] === true;
}
diff --git a/packages/react-async-states/src/devtools/Devtools.ts b/packages/react-async-states/src/devtools/Devtools.ts
index 5b87d7af..18a559d3 100644
--- a/packages/react-async-states/src/devtools/Devtools.ts
+++ b/packages/react-async-states/src/devtools/Devtools.ts
@@ -1,5 +1,10 @@
import {ProducerSavedProps, State, StateInterface} from "../async-state";
import {DevtoolsEvent, DevtoolsJournalEvent, DevtoolsRequest} from "./index";
+import {humanizeDevFlags} from "../react/utils";
+import {
+ AsyncStateSubscribeProps,
+ StateSubscription
+} from "../async-state/AsyncState";
let journalEventsId = 0;
const source = "async-states-agent";
@@ -32,9 +37,11 @@ interface DevtoolsInterface {
emitUnsubscription(instance: StateInterface, subscriptionKey: string),
- emitRunSync(instance: StateInterface, props: ProducerSavedProps): void,
+ emitRunSync(
+ instance: StateInterface, props: ProducerSavedProps): void,
- emitRunPromise(instance: StateInterface, props: ProducerSavedProps): void,
+ emitRunPromise(
+ instance: StateInterface, props: ProducerSavedProps): void,
emitRunGenerator(
instance: StateInterface, props: ProducerSavedProps): void,
@@ -43,7 +50,8 @@ interface DevtoolsInterface {
instance: StateInterface, props: ProducerSavedProps): void,
emitRunConsumedFromCache(
- instance: StateInterface, payload: Record | undefined | null,
+ instance: StateInterface,
+ payload: Record | undefined | null,
args: any[]
): void,
}
@@ -126,6 +134,9 @@ function createDevtools(): DevtoolsInterface {
}
function emitKeys() {
+ if (!connected) {
+ return;
+ }
emit({
source,
payload: keys,
@@ -192,6 +203,9 @@ function createDevtools(): DevtoolsInterface {
return;
}
retainStateInstance(asyncState);
+ if (!connected) {
+ return;
+ }
emit({
source,
uniqueId: asyncState.uniqueId,
@@ -204,15 +218,15 @@ function createDevtools(): DevtoolsInterface {
uniqueId: asyncState.uniqueId,
lastSuccess: asyncState.lastSuccess,
producerType: asyncState.producerType,
- subscriptions: asyncState.subscriptions ? Object.keys(asyncState.subscriptions) : [],
+ subscriptions: (asyncState.subscriptions ? Object.values(asyncState.subscriptions) : []).map(mapSubscriptionToDevtools),
lanes: asyncState.lanes ? Object.keys(asyncState.lanes).map(key => ({
uniqueId: asyncState.lanes![key].uniqueId,
key
})) : [],
- parent: {
+ parent: asyncState.parent ? {
key: asyncState.parent?.key,
uniqueId: asyncState.parent?.uniqueId
- },
+ }: null,
},
type: DevtoolsEvent.setAsyncState
});
@@ -234,6 +248,9 @@ function createDevtools(): DevtoolsInterface {
}
function emitPartialSync(uniqueId, evt) {
+ if (!connected) {
+ return;
+ }
emit({
source,
payload: evt,
@@ -256,18 +273,6 @@ function createDevtools(): DevtoolsInterface {
},
});
emitStateInterface(asyncState);
- // listenToDevtoolsMessages(asyncState);
- }
-
- function emitInsideProvider(asyncState: StateInterface, insideProvider = true) {
- if (asyncState.config.hideFromDevtools) {
- return;
- }
- retainStateInstance(asyncState);
- emitJournalEvent(asyncState, {
- payload: insideProvider,
- type: DevtoolsJournalEvent.insideProvider,
- });
}
function emitRunSync(asyncState: StateInterface, props) {
@@ -279,7 +284,11 @@ function createDevtools(): DevtoolsInterface {
payload: {props, type: "sync"},
type: DevtoolsJournalEvent.run
};
+
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -291,7 +300,8 @@ function createDevtools(): DevtoolsInterface {
});
}
- function emitRunConsumedFromCache(asyncState: StateInterface, payload, execArgs) {
+ function emitRunConsumedFromCache(
+ asyncState: StateInterface, payload, execArgs) {
if (asyncState.config.hideFromDevtools) {
return;
}
@@ -305,6 +315,9 @@ function createDevtools(): DevtoolsInterface {
type: DevtoolsJournalEvent.run
};
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -326,6 +339,9 @@ function createDevtools(): DevtoolsInterface {
type: DevtoolsJournalEvent.run
};
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -347,6 +363,9 @@ function createDevtools(): DevtoolsInterface {
type: DevtoolsJournalEvent.run
};
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -368,6 +387,9 @@ function createDevtools(): DevtoolsInterface {
type: DevtoolsJournalEvent.run
};
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -398,11 +420,20 @@ function createDevtools(): DevtoolsInterface {
return;
}
retainStateInstance(asyncState);
+ let subscription = asyncState.subscriptions![subKey];
let evt = {
- payload: subKey,
- type: DevtoolsJournalEvent.subscription
+ type: DevtoolsJournalEvent.subscription,
+ payload: {
+ key: subscription.props.key,
+ origin: subscription.props.origin,
+ flags: subscription.props.flags,
+ devFlags: humanizeDevFlags(subscription.props.flags || 0),
+ }
};
emitJournalEvent(asyncState, evt);
+ if (!connected) {
+ return;
+ }
emitPartialSync(asyncState.uniqueId, {
key: asyncState.key,
eventId: journalEventsId,
@@ -510,6 +541,32 @@ function createDevtools(): DevtoolsInterface {
}
}
+function mapSubscriptionToDevtools(sub: StateSubscription) {
+ return {
+ key: sub.props.key,
+ flags: sub.props.flags,
+ origin: getSubscriptionOrigin(sub.props.origin),
+ devFlags: humanizeDevFlags(sub.props.flags || 0),
+ }
+}
+
+function getSubscriptionOrigin(origin?: number) {
+ switch (origin) {
+ case 1:
+ return "useAsyncState";
+ case 2:
+ return "useSource";
+ case 3:
+ return "useProducer";
+ case 4:
+ return "useSelector";
+ case undefined:
+ return "undefined";
+ default:
+ return "unknown";
+ }
+}
+
let DEVTOOLS = createDevtools();
export default DEVTOOLS;
diff --git a/packages/react-async-states/src/index.ts b/packages/react-async-states/src/index.ts
index 2809dccc..9b7e3ad7 100644
--- a/packages/react-async-states/src/index.ts
+++ b/packages/react-async-states/src/index.ts
@@ -2,26 +2,23 @@ export {useSelector} from "./react/useSelector";
export {useRun, useRunLane} from "./react/useRun";
-export {useAsyncState} from "./react/useAsyncState";
-
-export {AsyncStateProvider} from "./react/AsyncStateProvider";
-
-export {useSource, useSourceLane, useProducer} from "./react/useAsyncStateBase";
+export {AsyncStateProvider} from "./react/Provider";
+export {
+ useAsyncState, useSource, useSourceLane, useProducer
+} from "./react/useAsyncState";
export {
createSource,
+ Status,
+ RunEffect,
ProducerType,
- AsyncStateStatus,
- ProducerRunEffects,
AsyncStateManager,
} from "./async-state";
-export {createLoaderProducer} from "./react/loader-producer"
export {
RenderStrategy,
- SubscriptionMode,
} from "./types.internal";
export {StateBoundary, useCurrentState} from "./react/StateBoundary";
@@ -47,7 +44,7 @@ export type {
InitialAsyncState,
AsyncStateSelector,
InitialStatesObject,
- HoistToProviderConfig,
+ hoistConfig,
ExtendedInitialAsyncState,
AsyncStateManagerInterface,
diff --git a/packages/react-async-states/src/react/AsyncStateProvider.tsx b/packages/react-async-states/src/react/Provider.tsx
similarity index 98%
rename from packages/react-async-states/src/react/AsyncStateProvider.tsx
rename to packages/react-async-states/src/react/Provider.tsx
index 6fdbdc16..6b7725bb 100644
--- a/packages/react-async-states/src/react/AsyncStateProvider.tsx
+++ b/packages/react-async-states/src/react/Provider.tsx
@@ -85,7 +85,7 @@ export function AsyncStateProvider(
function onInitialStatesChange(): { data: AsyncStateEntry[] } {
const output = Object.create(null);
- output.data = manager.setInitialStates(initialStates);
+ output.data = manager.setStates(initialStates);
return output;
}
diff --git a/packages/react-async-states/src/react/StateBoundary.tsx b/packages/react-async-states/src/react/StateBoundary.tsx
index a8c91e15..8c016082 100644
--- a/packages/react-async-states/src/react/StateBoundary.tsx
+++ b/packages/react-async-states/src/react/StateBoundary.tsx
@@ -1,11 +1,12 @@
import * as React from "react";
-import {AsyncStateStatus, State} from "../async-state";
+import {Status, State} from "../async-state";
import {
MixedConfig, RenderStrategy,
StateBoundaryProps,
UseAsyncState, UseAsyncStateConfiguration,
} from "../types.internal";
import {useAsyncState} from "./useAsyncState";
+import {emptyArray} from "./utils";
const StateBoundaryContext = React.createContext(null);
@@ -67,7 +68,6 @@ export function FetchAsYouRenderBoundary(props: StateBoundaryProps)
);
}
-const emptyArray = [];
function FetchThenRenderInitialBoundary({
dependencies = emptyArray, result, config
}: {dependencies?: any[], result: UseAsyncState, config: MixedConfig}) {
@@ -95,17 +95,17 @@ export function FetchThenRenderBoundary(props: StateBoundaryProps) {
const result = useAsyncState(props.config, props.dependencies);
switch (result.source?.getState().status) {
- case AsyncStateStatus.pending:
- case AsyncStateStatus.aborted:
- case AsyncStateStatus.initial: {
+ case Status.pending:
+ case Status.aborted:
+ case Status.initial: {
return ;
}
- case AsyncStateStatus.error:
- case AsyncStateStatus.success: {
+ case Status.error:
+ case Status.success: {
const children = inferBoundaryChildren(result, props);
return (
diff --git a/packages/react-async-states/src/react/StateHook.ts b/packages/react-async-states/src/react/StateHook.ts
new file mode 100644
index 00000000..4f0f6b3a
--- /dev/null
+++ b/packages/react-async-states/src/react/StateHook.ts
@@ -0,0 +1,651 @@
+import * as React from "react";
+import {
+ BaseConfig,
+ BaseUseAsyncState,
+ CleanupFn,
+ MixedConfig,
+ PartialUseAsyncStateConfiguration,
+ StateContextValue,
+ SubscribeEventProps,
+ UseAsyncState,
+ UseAsyncStateEventFn,
+ UseAsyncStateEvents,
+ UseAsyncStateEventSubscribe
+} from "../types.internal";
+import AsyncState, {
+ AbortFn,
+ Status,
+ Producer,
+ Source,
+ State,
+ StateInterface
+} from "../async-state";
+import {
+ AUTO_RUN,
+ CHANGE_EVENTS,
+ CONFIG_FUNCTION,
+ CONFIG_OBJECT,
+ CONFIG_SOURCE,
+ CONFIG_STRING,
+ EQUALITY_CHECK,
+ FORK,
+ HOIST,
+ INSIDE_PROVIDER,
+ LANE,
+ NO_MODE,
+ SELECTOR,
+ SOURCE,
+ STANDALONE,
+ SUBSCRIBE_EVENTS,
+ WAIT
+} from "./StateHookFlags";
+import {__DEV__, shallowClone} from "../shared";
+import {humanizeDevFlags} from "./utils";
+import {
+ readSource,
+ standaloneProducerEffectsCreator
+} from "../async-state/AsyncState";
+import {isSource} from "../async-state/utils";
+import {nextKey} from "../async-state/key-gen";
+import {computeCallerName} from "./helpers/useCallerName";
+
+export interface StateHook {
+ current: E,
+ flags: number,
+ name: string | undefined;
+ config: MixedConfig,
+ origin: number | undefined;
+ version: number | undefined,
+ base: BaseUseAsyncState,
+ context: StateContextValue | null,
+ subscribe: (
+ setGuard: React.Dispatch>,
+ onChange: () => void,
+ ) => AbortFn,
+
+ instance: StateInterface | null,
+
+ update(
+ origin: number,
+ newConfig: MixedConfig,
+ contextValue: StateContextValue | null,
+ overrides?: PartialUseAsyncStateConfiguration,
+ level?: number
+ ),
+}
+
+export class StateHookImpl implements StateHook {
+ current: E;
+ flags: number;
+ name: string | undefined;
+ config: MixedConfig;
+ origin: number | undefined;
+ version: number | undefined;
+ base: BaseUseAsyncState;
+ context: StateContextValue | null;
+ instance: StateInterface | null;
+
+ subscribe: (
+ setGuard: React.Dispatch>,
+ onChange: () => void,
+ ) => AbortFn
+
+ constructor() {
+ this.flags = NO_MODE;
+ this.context = null;
+ }
+
+ update(
+ origin: number,
+ newConfig: MixedConfig,
+ contextValue: StateContextValue | null,
+ overrides?: PartialUseAsyncStateConfiguration,
+ level?: number
+ ) {
+ let nextFlags = resolveFlags(newConfig, contextValue, overrides);
+ let instance = resolveInstance(nextFlags, newConfig, contextValue, this, overrides);
+
+ if (!instance && !(nextFlags & WAIT)) {
+ throw new Error("Mode isn't wait and instance isn't defined! this is a bug");
+ }
+
+ if (instance && (nextFlags & CONFIG_OBJECT && (newConfig as BaseConfig).payload)) {
+ instance.mergePayload((newConfig as BaseConfig).payload);
+ }
+ if (instance && (nextFlags & INSIDE_PROVIDER) && contextValue?.getPayload()) {
+ instance.mergePayload(contextValue.getPayload());
+ }
+
+ this.origin = origin;
+ this.flags = nextFlags;
+ this.config = newConfig;
+ this.instance = instance;
+ this.context = contextValue;
+ this.base = makeBaseReturn(this);
+ this.name = calculateSubscriptionKey(this, level);
+ this.subscribe = createSubscribeAndWatchFunction(this);
+ }
+}
+
+export function resolveFlags(
+ mixedConfig: MixedConfig,
+ contextValue: StateContextValue | null,
+ overrides?: PartialUseAsyncStateConfiguration,
+): number {
+
+ let flags = NO_MODE;
+ if (contextValue !== null) {
+ flags |= INSIDE_PROVIDER;
+ }
+
+ switch (typeof mixedConfig) {
+ case "function": {
+ return flags | CONFIG_FUNCTION | STANDALONE | getConfigFlags(overrides);
+ }
+
+ case "string": {
+ flags |= CONFIG_STRING | getConfigFlags(overrides);
+ if (!(flags & HOIST) && !(flags & INSIDE_PROVIDER)) {
+ return flags | STANDALONE;
+ }
+ if (!(flags & HOIST) && !contextValue!.get(mixedConfig)) {
+ return flags | WAIT;
+ }
+ return flags;
+ }
+
+ case "object": {
+ // attempt source first
+ let baseConfig = mixedConfig as BaseConfig;
+ if (isSource(baseConfig)) {
+ return flags | CONFIG_SOURCE | SOURCE | getConfigFlags(overrides);
+ } else if (isSource(baseConfig.source)) {
+ return flags | CONFIG_OBJECT | SOURCE | getConfigFlags(baseConfig) | getConfigFlags(overrides);
+ } else {
+ flags |= CONFIG_OBJECT | getConfigFlags(baseConfig) | getConfigFlags(overrides);
+ if (!(flags & HOIST) && !(flags & INSIDE_PROVIDER) || !baseConfig.key) {
+ return flags | STANDALONE;
+ }
+ if (!(flags & HOIST) && baseConfig.key && !contextValue!.get(baseConfig.key)) {
+ return flags | WAIT;
+ }
+ return flags;
+ }
+ }
+ default: {
+ return flags | STANDALONE | getConfigFlags(overrides);
+ }
+ }
+}
+
+function getKeyFlags(
+ config: PartialUseAsyncStateConfiguration,
+ key: string,
+) {
+ switch (key) {
+ case "hoist": return config.hoist ? HOIST : NO_MODE;
+ case "fork": return config.fork ? FORK : NO_MODE;
+ case "lane": return config.lane ? LANE : NO_MODE;
+ case "selector": return typeof config.selector === "function" ? SELECTOR : NO_MODE;
+ case "areEqual": return typeof config.areEqual === "function" ? EQUALITY_CHECK : NO_MODE;
+
+ case "events": {
+ let flags = NO_MODE;
+ if (config.events!.change) {
+ flags |= CHANGE_EVENTS;
+ }
+ if (config.events!.subscribe) {
+ flags |= SUBSCRIBE_EVENTS;
+ }
+ return flags;
+ }
+
+ case "lazy":
+ case "condition": {
+ if (config.lazy === false && config.condition !== false) {
+ return AUTO_RUN;
+ }
+ return NO_MODE;
+ }
+ default: return NO_MODE;
+ }
+}
+
+export function getConfigFlags(
+ config?: PartialUseAsyncStateConfiguration
+): number {
+ if (!config) {
+ return NO_MODE;
+ }
+ return Object.keys(config)
+ .reduce((flags, key) =>
+ (flags | getKeyFlags(config, key)), NO_MODE);
+}
+
+
+export function resolveInstance(
+ flags: number,
+ config: MixedConfig,
+ contextValue: StateContextValue | null,
+ previousHook: StateHook,
+ overrides?: PartialUseAsyncStateConfiguration
+): StateInterface | null {
+
+ if (flags & WAIT) {
+ return null;
+ }
+
+ if (flags & SOURCE) {
+ return resolveSourceInstance(flags, config, overrides);
+ }
+
+ if (flags & STANDALONE) {
+ return resolveStandaloneInstance(previousHook, flags, config);
+ }
+
+ if (flags & INSIDE_PROVIDER) {
+ return resolveProviderInstance(flags, contextValue, config);
+ }
+
+ return null;
+}
+
+
+function resolveSourceInstance(
+ flags: number,
+ config: MixedConfig,
+ overrides?: PartialUseAsyncStateConfiguration
+) {
+ if (flags & CONFIG_SOURCE) {
+ let instance = readSource(config as Source);
+ if (flags & FORK) {
+ instance = instance.fork();
+ }
+ if (flags & LANE) { // config is a source, so ofc doesn't contain lane prop
+ let laneKey = overrides?.lane;
+ instance = instance.getLane(laneKey);
+ }
+ return instance;
+ }
+
+ let givenConfig = config as BaseConfig;
+ let instance = readSource(givenConfig.source!);
+ if (flags & FORK) {
+ instance = instance.fork(givenConfig.forkConfig);
+ }
+ if (flags & LANE) {
+ let laneKey = (config as BaseConfig).lane || overrides?.lane;
+ return instance.getLane(laneKey)
+ }
+ return instance;
+}
+
+function resolveStandaloneInstance(
+ hook: StateHook,
+ flags: number,
+ config: MixedConfig
+) {
+
+ let canReuse = hook && !!hook.instance && !!(hook.flags & STANDALONE);
+ if (canReuse) {
+ patchInstance(hook.instance!, flags, config);
+ return hook.instance;
+ }
+
+ let key = readKeyFromConfig(flags, config, null);
+ let producer = readProducerFromConfig(flags, config);
+ let producerConfig = flags & CONFIG_OBJECT ? (config as BaseConfig) : undefined;
+
+ return new AsyncState(key, producer, producerConfig);
+}
+
+function resolveProviderInstance(
+ flags: number,
+ contextValue: StateContextValue | null,
+ config: MixedConfig
+) {
+ let key: string = flags & CONFIG_STRING
+ ? (config as string) : (config as BaseConfig).key!;
+
+ if (
+ flags & HOIST &&
+ flags & CONFIG_OBJECT &&
+ (config as BaseConfig).hoistConfig?.override) {
+ // do not check on existing because it is guaranteed to exist
+ // or else we would have a WAIT flag and quit earlier!
+ let key = readKeyFromConfig(flags, config, null);
+ let producer = readProducerFromConfig(flags, config);
+ let producerConfig = flags & CONFIG_OBJECT ? (config as BaseConfig) : undefined;
+
+ return new AsyncState(key, producer, producerConfig);
+ }
+
+ let instance = contextValue!.get(key);
+ if (instance) {
+ if (flags & FORK) {
+ instance = instance.fork((config as BaseConfig).forkConfig);
+ }
+ if (flags & LANE) {
+ return instance.getLane((config as BaseConfig).lane!)
+ }
+ return instance;
+ } else {
+ let key = readKeyFromConfig(flags, config, null);
+ let producer = readProducerFromConfig(flags, config);
+ let producerConfig = flags & CONFIG_OBJECT ? (config as BaseConfig) : undefined;
+
+ return new AsyncState(key, producer, producerConfig);
+ }
+}
+
+function patchInstance(
+ instance: StateInterface,
+ flags: number,
+ config: MixedConfig
+) {
+ let key = readKeyFromConfig(flags, config, instance);
+ let producer = readProducerFromConfig(flags, config);
+ let producerConfig = flags & CONFIG_OBJECT ? (config as BaseConfig) : undefined;
+
+ instance.key = key;
+ instance.replaceProducer(producer);
+ instance.patchConfig(producerConfig);
+}
+
+function readKeyFromConfig(
+ flags: number,
+ config: MixedConfig,
+ prevInstance: StateInterface | null
+): string {
+ if (flags & CONFIG_STRING) {
+ return config as string;
+ }
+
+ if (flags & CONFIG_OBJECT && (config as BaseConfig).key) {
+ return (config as BaseConfig).key!;
+ }
+
+ if (!prevInstance) {
+ return nextKey();
+ }
+
+ return prevInstance.key;
+}
+
+
+function readProducerFromConfig(
+ flags: number,
+ config: MixedConfig,
+): Producer | undefined {
+ if (flags & CONFIG_FUNCTION) {
+ return config as Producer;
+ }
+
+ if (flags & CONFIG_OBJECT) {
+ return (config as BaseConfig).producer;
+ }
+
+ return undefined;
+}
+
+
+export function makeBaseReturn(hook: StateHook) {
+ if (!hook.instance) {
+ let {flags, config} = hook;
+ let key = flags & CONFIG_STRING ? config : (config as BaseConfig