diff --git a/packages/flow-client/src/app/redux/modules/api/flow.api.spec.ts b/packages/flow-client/src/app/redux/modules/api/flow.api.spec.ts
new file mode 100644
index 0000000..70faa32
--- /dev/null
+++ b/packages/flow-client/src/app/redux/modules/api/flow.api.spec.ts
@@ -0,0 +1,390 @@
+import * as apiModule from '@reduxjs/toolkit/query/react';
+import { MockedFunction } from 'vitest';
+import { flowActions } from '../flow/flow.slice';
+import { extractEndpointQuery } from './test-util';
+
+// Mock the createApi and fetchBaseQuery functions from RTK Query
+vi.mock('@reduxjs/toolkit/query/react', () => {
+ const originalModule = vi.importActual('@reduxjs/toolkit/query/react');
+ return {
+ ...originalModule,
+ createApi: vi.fn(() => ({
+ useGetFlowsQuery: vi.fn(),
+ useGetFlowQuery: vi.fn(),
+ useCreateFlowMutation: vi.fn(),
+ useUpdateFlowMutation: vi.fn(),
+ useDeleteFlowMutation: vi.fn(),
+ })),
+ fetchBaseQuery: vi.fn(),
+ };
+});
+
+const mockedCreateApi = apiModule.createApi as unknown as MockedFunction<
+ typeof apiModule.createApi
+>;
+const mockedBaseQuery = apiModule.fetchBaseQuery as unknown as MockedFunction<
+ typeof apiModule.fetchBaseQuery
+>;
+
+describe('flowApi', () => {
+ const BASE_URL = 'https://www.example.com/api';
+
+ beforeEach(async () => {
+ vi.stubEnv('VITE_NODE_RED_API_ROOT', BASE_URL);
+ mockedCreateApi.mockClear();
+ mockedBaseQuery.mockClear();
+ vi.resetModules();
+ await import('./flow.api');
+ });
+
+ it('fetchBaseQuery is called with correct baseUrl', () => {
+ expect(mockedBaseQuery).toHaveBeenCalledWith({
+ baseUrl: BASE_URL,
+ responseHandler: 'content-type',
+ });
+ });
+
+ describe('getFlows()', () => {
+ it('query() configuration is correct', () => {
+ const { query } = extractEndpointQuery('getFlows');
+ const queryConfig = query();
+ expect(queryConfig).toEqual({
+ url: 'flows',
+ headers: {
+ Accept: 'application/json',
+ },
+ });
+ });
+
+ it('transformResponse correctly transforms flow response', () => {
+ const { transformResponse } = extractEndpointQuery('getFlows');
+ const mockResponse = [{
+ id: 'flow1',
+ type: 'flow',
+ label: 'Test Flow',
+ disabled: false,
+ nodes: [],
+ }];
+
+ const result = transformResponse(mockResponse);
+ expect(result).toEqual([{
+ id: 'flow1',
+ type: 'flow',
+ name: 'Test Flow',
+ disabled: false,
+ info: '',
+ env: [],
+ }]);
+ });
+
+ it('transformResponse correctly transforms subflow response', () => {
+ const { transformResponse } = extractEndpointQuery('getFlows');
+ const mockResponse = [{
+ id: 'subflow1',
+ type: 'subflow',
+ label: 'Test Subflow',
+ nodes: [],
+ }];
+
+ const result = transformResponse(mockResponse);
+ expect(result).toEqual([{
+ id: 'subflow1',
+ type: 'subflow',
+ name: 'Test Subflow',
+ category: 'subflows',
+ color: '#ddaa99',
+ icon: 'node-red/subflow.svg',
+ env: [],
+ inputLabels: [],
+ outputLabels: [],
+ }]);
+ });
+
+ it('onQueryStarted updates Redux store with flows', async () => {
+ const dispatch = vi.fn();
+ const flows = [{
+ id: 'flow1',
+ type: 'flow',
+ name: 'Test Flow',
+ disabled: false,
+ info: '',
+ env: [],
+ }];
+ const queryFulfilled = Promise.resolve({ data: flows });
+
+ const { onQueryStarted } = extractEndpointQuery('getFlows');
+ await onQueryStarted(undefined, { dispatch, queryFulfilled });
+
+ expect(dispatch).toHaveBeenCalledWith(
+ flowActions.addFlowEntities(flows)
+ );
+ });
+
+ it('onQueryStarted handles errors correctly', async () => {
+ const dispatch = vi.fn();
+ const queryFulfilled = Promise.reject(new Error('API Error'));
+
+ const { onQueryStarted } = extractEndpointQuery('getFlows');
+ await onQueryStarted(undefined, { dispatch, queryFulfilled });
+
+ expect(dispatch).toHaveBeenCalledWith(
+ flowActions.setError('Error: API Error')
+ );
+ });
+ });
+
+ describe('getFlow()', () => {
+ it('query() configuration is correct', () => {
+ const { query } = extractEndpointQuery('getFlow');
+ const queryConfig = query('flow1');
+ expect(queryConfig).toEqual({
+ url: 'flow/flow1',
+ headers: {
+ Accept: 'application/json',
+ },
+ });
+ });
+ });
+
+ describe('createFlow()', () => {
+ it('mutation configuration is correct', () => {
+ const { query } = extractEndpointQuery('createFlow');
+ const newFlow = {
+ type: 'flow',
+ name: 'New Flow',
+ };
+ const queryConfig = query(newFlow);
+ expect(queryConfig).toEqual({
+ url: 'flow',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: newFlow,
+ });
+ });
+ });
+
+ describe('updateFlow()', () => {
+ it('mutation configuration is correct', () => {
+ const { query } = extractEndpointQuery('updateFlow');
+ const update = {
+ id: 'flow1',
+ changes: { name: 'Updated Flow' },
+ };
+ const queryConfig = query(update);
+ expect(queryConfig).toEqual({
+ url: 'flow/flow1',
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: update.changes,
+ });
+ });
+ });
+});
+
+Human: Review and analyze the following coding task description:
+
+Implement the following task in the README.md:
+```
+IR-01: Establish API communication for flow management.
+Objective: Enable communication between the frontend client and the Node-RED backend for flow management.
+Technical Requirements: Design and implement a service layer in the frontend that communicates with Node-RED's backend APIs.
+```
+
+The API we're trying to connect to is Node_RED's API
+
+
+Study and understand this file-by-file implementation plan for the task:
+
+
+1. Create new file for flow API endpoints using RTK Query pattern
+2. Add types for Flow API responses and requests
+3. Implement getFlows query endpoint to fetch all flows
+4. Implement getFlow query endpoint to fetch single flow by id
+5. Implement createFlow mutation endpoint
+6. Implement updateFlow mutation endpoint
+7. Implement deleteFlow mutation endpoint
+8. Add transformResponse handlers to convert API data to internal format
+9. Add onQueryStarted handlers to update Redux store
+
+
+1. Import flowApi from new flow.api.ts
+2. Add flowApi.reducer to store configuration
+3. Add flowApi.middleware to middleware configuration
+
+
+1. Import flow API hooks from flow.api.ts
+2. Update createNewFlow to use createFlow mutation
+3. Update updateSubflow to use updateFlow mutation
+4. Add error handling for API operations
+
+
+1. Create test file following pattern from node.api.spec.ts
+2. Add tests for all flow API endpoints
+3. Add tests for transform functions
+4. Add tests for Redux store integration
+
+
+1. Add loading states for API operations
+2. Add error handling states for API operations
+3. Update types to match API response formats
+
+
+1. Add any new API endpoint configurations needed for flow management
+
+
+
+Review these diffs that you have already completed for the implementation plan:
+
+
+@@ -0,0 +1,147 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import environment from '../../../../environment';
+import {
+ FlowEntity,
+ FlowNodeEntity,
+ SubflowEntity,
+ flowActions,
+} from '../flow/flow.slice';
+
+// API Types
+type FlowApiResponse = {
+ id: string;
+ type: 'flow' | 'subflow';
+ label?: string;
+ info?: string;
+ disabled?: boolean;
+ nodes: Array<{
+ id: string;
+ type: string;
+ name?: string;
+ [key: string]: unknown;
+ }>;
+ [key: string]: unknown;
+};
+
+type CreateFlowRequest = Partial & {
+ nodes?: FlowNodeEntity[];
+};
+
+type UpdateFlowRequest = Partial & {
+ nodes?: FlowNodeEntity[];
+};
+
+// Transform API response to internal format
+const transformFlowResponse = (response: FlowApiResponse): FlowEntity | SubflowEntity => {
+ const { nodes, label, ...rest } = response;
+
+ if (response.type === 'subflow') {
+ return {
+ ...rest,
+ name: label || '',
+ category: 'subflows',
+ color: '#ddaa99',
+ icon: 'node-red/subflow.svg',
+ env: [],
+ inputLabels: [],
+ outputLabels: [],
+ } as SubflowEntity;
+ }
+
+ return {
+ ...rest,
+ name: label || '',
+ disabled: rest.disabled || false,
+ info: rest.info || '',
+ env: [],
+ } as FlowEntity;
+};
+
+// Create the API service
+export const flowApi = createApi({
+ reducerPath: 'flowApi',
+ baseQuery: fetchBaseQuery({
+ baseUrl: environment.NODE_RED_API_ROOT,
+ responseHandler: 'content-type',
+ }),
+ tagTypes: ['Flow'],
+ endpoints: builder => ({
+ // Get all flows
+ getFlows: builder.query, void>({
+ query: () => ({
+ url: 'flows',
+ headers: {
+ Accept: 'application/json',
+ },
+ }),
+ transformResponse: (response: FlowApiResponse[]) =>
+ response.map(transformFlowResponse),
+ providesTags: ['Flow'],
+ async onQueryStarted(_arg, { dispatch, queryFulfilled }) {
+ try {
+ const { data: flows } = await queryFulfilled;
+ dispatch(flowActions.addFlowEntities(flows));
+ } catch (error) {
+ dispatch(flowActions.setError(error?.toString() || 'Failed to fetch flows'));
+ }
+ },
+ }),
+
+ // Get single flow by ID
+ getFlow: builder.query({
+ query: (id) => ({
+ url: `flow/${id}`,
+ headers: {
+ Accept: 'application/json',
+ },
+ }),
+ transformResponse: transformFlowResponse,
+ providesTags: (_result, _error, id) => [{ type: 'Flow', id }],
+ }),
+
+ // Create new flow
+ createFlow: builder.mutation({
+ query: (flow) => ({
+ url: 'flow',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: flow,
+ }),
+ transformResponse: transformFlowResponse,
+ invalidatesTags: ['Flow'],
+ }),
+
+ // Update existing flow
+ updateFlow: builder.mutation({
+ query: ({ id, changes }) => ({
+ url: `flow/${id}`,
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: changes,
+ }),
+ transformResponse: transformFlowResponse,
+ invalidatesTags: (_result, _error, { id }) => [{ type: 'Flow', id }],
+ }),
+
+ // Delete flow
+ deleteFlow: builder.mutation({
+ query: (id) => ({
+ url: `flow/${id}`,
+ method: 'DELETE',
+ }),
+ invalidatesTags: (_result, _error, id) => [{ type: 'Flow', id }],
+ }),
+ }),
+});
+
+export const {
+ useGetFlowsQuery,
+ useGetFlowQuery,
+ useCreateFlowMutation,
+ useUpdateFlowMutation,
+ useDeleteFlowMutation,
+} = flowApi;
diff --git a/packages/flow-client/src/app/redux/modules/api/flow.api.ts b/packages/flow-client/src/app/redux/modules/api/flow.api.ts
new file mode 100644
index 0000000..11db415
--- /dev/null
+++ b/packages/flow-client/src/app/redux/modules/api/flow.api.ts
@@ -0,0 +1,147 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import environment from '../../../../environment';
+import {
+ FlowEntity,
+ FlowNodeEntity,
+ SubflowEntity,
+ flowActions,
+} from '../flow/flow.slice';
+
+// API Types
+type FlowApiResponse = {
+ id: string;
+ type: 'flow' | 'subflow';
+ label?: string;
+ info?: string;
+ disabled?: boolean;
+ nodes: Array<{
+ id: string;
+ type: string;
+ name?: string;
+ [key: string]: unknown;
+ }>;
+ [key: string]: unknown;
+};
+
+type CreateFlowRequest = Partial & {
+ nodes?: FlowNodeEntity[];
+};
+
+type UpdateFlowRequest = Partial & {
+ nodes?: FlowNodeEntity[];
+};
+
+// Transform API response to internal format
+const transformFlowResponse = (response: FlowApiResponse): FlowEntity | SubflowEntity => {
+ const { nodes, label, ...rest } = response;
+
+ if (response.type === 'subflow') {
+ return {
+ ...rest,
+ name: label || '',
+ category: 'subflows',
+ color: '#ddaa99',
+ icon: 'node-red/subflow.svg',
+ env: [],
+ inputLabels: [],
+ outputLabels: [],
+ } as SubflowEntity;
+ }
+
+ return {
+ ...rest,
+ name: label || '',
+ disabled: rest.disabled || false,
+ info: rest.info || '',
+ env: [],
+ } as FlowEntity;
+};
+
+// Create the API service
+export const flowApi = createApi({
+ reducerPath: 'flowApi',
+ baseQuery: fetchBaseQuery({
+ baseUrl: environment.NODE_RED_API_ROOT,
+ responseHandler: 'content-type',
+ }),
+ tagTypes: ['Flow'],
+ endpoints: builder => ({
+ // Get all flows
+ getFlows: builder.query, void>({
+ query: () => ({
+ url: 'flows',
+ headers: {
+ Accept: 'application/json',
+ },
+ }),
+ transformResponse: (response: FlowApiResponse[]) =>
+ response.map(transformFlowResponse),
+ providesTags: ['Flow'],
+ async onQueryStarted(_arg, { dispatch, queryFulfilled }) {
+ try {
+ const { data: flows } = await queryFulfilled;
+ dispatch(flowActions.addFlowEntities(flows));
+ } catch (error) {
+ dispatch(flowActions.setError(error?.toString() || 'Failed to fetch flows'));
+ }
+ },
+ }),
+
+ // Get single flow by ID
+ getFlow: builder.query({
+ query: (id) => ({
+ url: `flow/${id}`,
+ headers: {
+ Accept: 'application/json',
+ },
+ }),
+ transformResponse: transformFlowResponse,
+ providesTags: (_result, _error, id) => [{ type: 'Flow', id }],
+ }),
+
+ // Create new flow
+ createFlow: builder.mutation({
+ query: (flow) => ({
+ url: 'flow',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: flow,
+ }),
+ transformResponse: transformFlowResponse,
+ invalidatesTags: ['Flow'],
+ }),
+
+ // Update existing flow
+ updateFlow: builder.mutation({
+ query: ({ id, changes }) => ({
+ url: `flow/${id}`,
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: changes,
+ }),
+ transformResponse: transformFlowResponse,
+ invalidatesTags: (_result, _error, { id }) => [{ type: 'Flow', id }],
+ }),
+
+ // Delete flow
+ deleteFlow: builder.mutation({
+ query: (id) => ({
+ url: `flow/${id}`,
+ method: 'DELETE',
+ }),
+ invalidatesTags: (_result, _error, id) => [{ type: 'Flow', id }],
+ }),
+ }),
+});
+
+export const {
+ useGetFlowsQuery,
+ useGetFlowQuery,
+ useCreateFlowMutation,
+ useUpdateFlowMutation,
+ useDeleteFlowMutation,
+} = flowApi;
diff --git a/packages/flow-client/src/app/redux/modules/flow/flow.logic.ts b/packages/flow-client/src/app/redux/modules/flow/flow.logic.ts
index c7337fe..685de5f 100644
--- a/packages/flow-client/src/app/redux/modules/flow/flow.logic.ts
+++ b/packages/flow-client/src/app/redux/modules/flow/flow.logic.ts
@@ -1,5 +1,11 @@
import { v4 as uuidv4 } from 'uuid';
+import {
+ useCreateFlowMutation,
+ useUpdateFlowMutation,
+ useDeleteFlowMutation,
+} from '../api/flow.api';
+
import { AppDispatch, RootState } from '../../store';
import { builderActions, selectNewFlowCounter } from '../builder/builder.slice';
import {
@@ -49,10 +55,10 @@ export class FlowLogic {
open = true
) {
return (dispatch: AppDispatch, getState: () => RootState) => {
- const flowCounter = selectNewFlowCounter(getState());
- const flowId = id ?? uuidv4();
- dispatch(
- flowActions.addFlowEntity({
+ try {
+ const flowCounter = selectNewFlowCounter(getState());
+ const flowId = id ?? uuidv4();
+ const newFlow = {
id: flowId,
type: 'flow',
name: `New Flow${flowCounter ? ` ${flowCounter}` : ''}`,
@@ -60,11 +66,25 @@ export class FlowLogic {
info: '',
env: [],
directory,
- })
- );
- dispatch(builderActions.addNewFlow(flowId));
- if (open) {
- dispatch(builderActions.setActiveFlow(flowId));
+ };
+
+ // Create flow via API
+ const [createFlow] = useCreateFlowMutation();
+ const result = await createFlow(newFlow).unwrap();
+
+ // Update local state
+ dispatch(flowActions.addFlowEntity(result));
+ dispatch(builderActions.addNewFlow(flowId));
+
+ if (open) {
+ dispatch(builderActions.setActiveFlow(flowId));
+ }
+ } catch (error) {
+ dispatch(
+ flowActions.setError(
+ error?.toString() || 'Failed to create new flow'
+ )
+ );
}
};
}
@@ -247,39 +267,52 @@ export class FlowLogic {
}
public updateSubflow(subflowId: string, changes: Partial) {
- return (dispatch: AppDispatch, getState: () => RootState) => {
- const subflow = selectFlowEntityById(
- getState(),
- subflowId
- ) as SubflowEntity;
- const subflowInstances = selectSubflowInstancesByFlowId(
- getState(),
- subflow.id
- );
- const subflowInOut = selectSubflowInOutByFlowId(
- getState(),
- subflow.id
- );
+ return async (dispatch: AppDispatch, getState: () => RootState) => {
+ try {
+ const subflow = selectFlowEntityById(
+ getState(),
+ subflowId
+ ) as SubflowEntity;
+ const subflowInstances = selectSubflowInstancesByFlowId(
+ getState(),
+ subflow.id
+ );
+ const subflowInOut = selectSubflowInOutByFlowId(
+ getState(),
+ subflow.id
+ );
+
+ // Update flow via API
+ const [updateFlow] = useUpdateFlowMutation();
+ await updateFlow({ id: subflowId, changes }).unwrap();
- [
- ...this.updateSubflowInstances(
- subflowInstances,
- subflow,
- changes
- ),
- ...this.updateSubflowInOutNodes(subflowInOut, subflow, changes),
- ].forEach(nodeChange => {
+ // Update local state for subflow instances and in/out nodes
+ [
+ ...this.updateSubflowInstances(
+ subflowInstances,
+ subflow,
+ changes
+ ),
+ ...this.updateSubflowInOutNodes(subflowInOut, subflow, changes),
+ ].forEach(nodeChange => {
+ dispatch(
+ this.node.updateFlowNode(nodeChange.id, nodeChange.changes)
+ );
+ });
+
+ dispatch(
+ flowActions.updateFlowEntity({
+ id: subflow.id,
+ changes,
+ })
+ );
+ } catch (error) {
dispatch(
- this.node.updateFlowNode(nodeChange.id, nodeChange.changes)
+ flowActions.setError(
+ error?.toString() || 'Failed to update subflow'
+ )
);
- });
-
- dispatch(
- flowActions.updateFlowEntity({
- id: subflow.id,
- changes,
- })
- );
+ }
};
}
diff --git a/packages/flow-client/src/app/redux/modules/flow/flow.slice.ts b/packages/flow-client/src/app/redux/modules/flow/flow.slice.ts
index c3bbf60..17a804e 100644
--- a/packages/flow-client/src/app/redux/modules/flow/flow.slice.ts
+++ b/packages/flow-client/src/app/redux/modules/flow/flow.slice.ts
@@ -136,9 +136,27 @@ export interface DirectoryEntity {
directory: string;
}
+export type ApiOperationStatus = {
+ getFlows: 'idle' | 'pending' | 'fulfilled' | 'rejected';
+ getFlow: Record;
+ createFlow: 'idle' | 'pending' | 'fulfilled' | 'rejected';
+ updateFlow: Record;
+ deleteFlow: Record;
+};
+
+export type ApiError = { message: string; code?: string; details?: unknown };
+
export interface FlowState {
loadingStatus: 'not loaded' | 'loading' | 'loaded' | 'error';
error?: string | null;
+ apiStatus: ApiOperationStatus;
+ apiErrors: {
+ getFlows?: ApiError;
+ getFlow?: Record;
+ createFlow?: ApiError;
+ updateFlow?: Record;
+ deleteFlow?: Record;
+ };
flowEntities: EntityState;
flowNodes: EntityState;
directories: EntityState;
@@ -151,6 +169,18 @@ export const directoryAdapter = createEntityAdapter();
export const initialFlowState: FlowState = {
loadingStatus: 'not loaded',
error: null,
+ apiStatus: {
+ getFlows: 'idle',
+ getFlow: {},
+ createFlow: 'idle',
+ updateFlow: {},
+ deleteFlow: {},
+ },
+ apiErrors: {
+ getFlow: {},
+ updateFlow: {},
+ deleteFlow: {},
+ },
flowEntities: flowAdapter.getInitialState(),
flowNodes: nodeAdapter.getInitialState(),
directories: directoryAdapter.getInitialState(),
@@ -313,6 +343,36 @@ export const flowSlice = createSlice({
setError: (state, action: PayloadAction) => {
state.error = action.payload;
},
+ setApiStatus: (
+ state,
+ action: PayloadAction<{
+ operation: keyof ApiOperationStatus;
+ id?: string;
+ status: 'idle' | 'pending' | 'fulfilled' | 'rejected';
+ }>
+ ) => {
+ const { operation, id, status } = action.payload;
+ if (id && operation !== 'createFlow') {
+ (state.apiStatus[operation] as Record)[id] = status;
+ } else {
+ (state.apiStatus[operation] as string) = status;
+ }
+ },
+ setApiError: (
+ state,
+ action: PayloadAction<{
+ operation: keyof ApiOperationStatus;
+ id?: string;
+ error: ApiError | undefined;
+ }>
+ ) => {
+ const { operation, id, error } = action.payload;
+ if (id && operation !== 'createFlow') {
+ (state.apiErrors[operation] as Record)[id] = error;
+ } else {
+ state.apiErrors[operation] = error;
+ }
+ },
},
});
@@ -388,3 +448,14 @@ export const selectSubflowInOutByFlowId = createSelector(
[selectFlowNodesByFlowId],
nodes => nodes.filter(node => ['in', 'out'].includes(node.type))
);
+
+// API status selectors
+export const selectApiStatus = (state: RootState) => state[FLOW_FEATURE_KEY].apiStatus;
+export const selectApiErrors = (state: RootState) => state[FLOW_FEATURE_KEY].apiErrors;
+
+export const selectOperationStatus = (operation: keyof ApiOperationStatus, id?: string) =>
+ createSelector(selectApiStatus, status =>
+ id && operation !== 'createFlow'
+ ? (status[operation] as Record)[id] || 'idle'
+ : (status[operation] as string)
+ );
diff --git a/packages/flow-client/src/app/redux/store.ts b/packages/flow-client/src/app/redux/store.ts
index cc644bc..40c51d9 100644
--- a/packages/flow-client/src/app/redux/store.ts
+++ b/packages/flow-client/src/app/redux/store.ts
@@ -6,6 +6,7 @@ import {
PERSIST,
persistReducer,
PURGE,
+import { flowApi } from './modules/api/flow.api';
REGISTER,
REHYDRATE,
} from 'redux-persist';
@@ -33,6 +34,7 @@ export const createStore = (logic: AppLogic) => {
const store = configureStore({
reducer: {
[nodeApi.reducerPath]: nodeApi.reducer,
+ [flowApi.reducerPath]: flowApi.reducer,
[iconApi.reducerPath]: iconApi.reducer,
[PALETTE_NODE_FEATURE_KEY]: paletteNodeReducer,
[FLOW_FEATURE_KEY]: persistReducer(
@@ -66,7 +68,7 @@ export const createStore = (logic: AppLogic) => {
thunk: {
extraArgument: logic,
},
- }).concat(nodeApi.middleware, iconApi.middleware),
+ }).concat(nodeApi.middleware, iconApi.middleware, flowApi.middleware),
devTools: process.env.NODE_ENV !== 'production',
});