PromucFlow_constructor/.cursor/docs/references/technical-details.md
vivek-appsmith d176e40726
refactor: restructure .cursor directory for improved organization and clarity (#40196)
# refactor: restructure .cursor directory for improved organization and
clarity

## Description

This PR refactors the `.cursor` directory to enhance organization,
clarity, and maintainability.

### Problem

The existing `.cursor` directory lacked clear organization, making it
difficult to find specific files, understand their purpose, and add new
components consistently.

### Solution

A comprehensive restructuring:

#### New Directory Structure

```
.cursor/
├── settings.json                  # Main configuration file
├── docs/                          # Documentation
│   ├── guides/                    # In-depth guides
│   ├── references/                # Quick references
│   └── practices/                 # Best practices
├── rules/                         # Rule definitions
│   ├── commit/                    # Commit-related rules
│   ├── quality/                   # Code quality rules
│   ├── testing/                   # Testing rules
│   └── verification/              # Verification rules
└── hooks/                         # Git hooks and scripts
```

#### Key Changes

1. **Logical Categorization**: Organized files into clear categories
based on purpose
2. **Improved Documentation**: Added comprehensive README files for each
directory
3. **Standardized Naming**: Implemented consistent kebab-case naming
convention
4. **Reference Updates**: Updated all internal references to point to
new file locations

### Benefits

- **Easier Navigation**: Clear categorization makes finding files
intuitive
- **Improved Understanding**: Comprehensive documentation explains
purpose and usage
- **Simplified Maintenance**: Logical structure makes updates and
additions easier
- **Better Onboarding**: New team members can quickly understand the
system

This refactoring sets a solid foundation for all Cursor AI-related
configurations and rules, making it easier for the team to leverage
Cursor's capabilities.
2025-04-11 12:04:33 +05:30

18 KiB

Appsmith Technical Details

This document provides in-depth technical information about the Appsmith codebase, focusing on implementation details, design patterns, and technologies used. This information will help Cursor AI better understand the code at a deeper level.

Technology Stack

Frontend

  • Framework: React 17+
  • State Management: Redux with Redux-Saga
  • Language: TypeScript 4+
  • Styling: Styled Components with Tailwind CSS
  • Build Tool: Webpack
  • Testing: Jest, React Testing Library, Cypress
  • Form Management: Formik
  • API Client: Axios
  • UI Components: Custom component library

Backend

  • Framework: Spring Boot 2.x
  • Language: Java 11+
  • Database: MongoDB
  • Reactive Programming: Project Reactor
  • Security: Spring Security
  • API Documentation: Swagger/OpenAPI
  • Caching: Redis

Key Frontend Implementation Details

State Management

The application uses Redux with a sophisticated structure:

// Example action
export const fetchDatasources = (applicationId: string) => ({
  type: ReduxActionTypes.FETCH_DATASOURCES,
  payload: { applicationId },
});

// Example reducer
const datasourceReducer = (state = initialState, action: ReduxAction<any>) => {
  switch (action.type) {
    case ReduxActionTypes.FETCH_DATASOURCES_SUCCESS:
      return { ...state, list: action.payload };
    // ...
  }
};

// Example saga
function* fetchDatasourcesSaga(action: ReduxAction<{ applicationId: string }>) {
  try {
    const response = yield call(DatasourcesApi.fetchDatasources, action.payload.applicationId);
    yield put({
      type: ReduxActionTypes.FETCH_DATASOURCES_SUCCESS,
      payload: response.data,
    });
  } catch (error) {
    yield put({
      type: ReduxActionTypes.FETCH_DATASOURCES_ERROR,
      payload: { error },
    });
  }
}

Widget System

Widgets are the building blocks of the application UI. They follow a standard structure:

export type WidgetProps = {
  widgetId: string;
  type: string;
  widgetName: string;
  parentId?: string;
  renderMode: RenderMode;
  version: number;
  // ...other properties
};

export default class ButtonWidget extends BaseWidget<ButtonWidgetProps, WidgetState> {
  static getPropertyPaneConfig() {
    return [
      {
        sectionName: "General",
        children: [
          {
            propertyName: "text",
            label: "Label",
            controlType: "INPUT_TEXT",
            // ...
          },
          // ...other properties
        ],
      },
      // ...other sections
    ];
  }
  
  getPageView() {
    return (
      <ButtonComponent
        // ...props
        onClick={this.handleClick}
      />
    );
  }
  
  handleClick = () => {
    if (this.props.onClick) {
      super.executeAction({
        triggerPropertyName: "onClick",
        dynamicString: this.props.onClick,
        event: {
          type: EventType.ON_CLICK,
          // ...
        },
      });
    }
  };
}

Property Pane System

The property pane is dynamically generated based on the widget configuration:

export const PropertyPaneView = (props: PropertyPaneViewProps) => {
  const { config, panel } = props;
  
  // Render property sections
  return (
    <PropertyPaneContainer>
      {config.map((section) => (
        <PropertySection key={section.sectionName} title={section.sectionName}>
          {section.children.map((property) => (
            <PropertyControl
              key={property.propertyName}
              propertyName={property.propertyName}
              controlType={property.controlType}
              // ...other props
            />
          ))}
        </PropertySection>
      ))}
    </PropertyPaneContainer>
  );
};

Data Binding

The app uses a JS evaluation engine to bind data to widgets:

export function evaluateDynamicValue(
  dynamicValue: string,
  data: Record<string, unknown>,
): any {
  // Set up execution environment
  const scriptToEvaluate = `
    function evaluation() {
      const $ = ${JSON.stringify(data)};
      try {
        return ${dynamicValue};
      } catch (e) {
        return undefined;
      }
    }
    evaluation();
  `;
  
  try {
    return eval(scriptToEvaluate);
  } catch (e) {
    return undefined;
  }
}

API Integration

The API client is set up with Axios and handles authentication:

const axiosInstance = axios.create({
  baseURL: "/api/v1",
  headers: {
    "Content-Type": "application/json",
  },
});

// Request interceptor for adding auth token
axiosInstance.interceptors.request.use((config) => {
  const token = localStorage.getItem("AUTH_TOKEN");
  if (token) {
    // Basic validation - check if token is a valid JWT format
    if (token.split('.').length === 3) {
      config.headers.Authorization = `Bearer ${token}`;
    } else {
      // Handle invalid token - could log user out or refresh token
      store.dispatch(refreshToken());
    }
  }
  return config;
});

// Response interceptor for handling errors
axiosInstance.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response && error.response.status === 401) {
      // Handle unauthorized
      store.dispatch(logoutUser());
    }
    return Promise.reject(error);
  }
);

Key Backend Implementation Details

Repository Pattern

Mongo repositories use reactive programming:

@Repository
public interface DatasourceRepository extends ReactiveMongoRepository<Datasource, String> {
    Mono<Datasource> findByNameAndOrganizationId(String name, String organizationId);
    Flux<Datasource> findAllByOrganizationId(String organizationId);
    Mono<Long> countByNameAndOrganizationId(String name, String organizationId);
}

Service Layer

Services handle business logic:

@Service
@RequiredArgsConstructor
public class DatasourceServiceImpl implements DatasourceService {
    private final DatasourceRepository repository;
    private final PluginService pluginService;
    
    @Override
    public Mono<Datasource> create(Datasource datasource) {
        return repository.save(datasource)
                .flatMap(saved -> pluginService.getById(datasource.getPluginId())
                        .map(plugin -> {
                            saved.setPlugin(plugin);
                            return saved;
                        }));
    }
    
    // Other methods...
}

Controller Layer

Controllers expose REST APIs:

@RestController
@RequestMapping("/api/v1/datasources")
@RequiredArgsConstructor
public class DatasourceController {
    private final DatasourceService service;
    
    @PostMapping
    public Mono<ResponseDTO<Datasource>> create(@RequestBody DatasourceDTO dto) {
        Datasource datasource = new Datasource();
        BeanUtils.copyProperties(dto, datasource);
        
        return service.create(datasource)
                .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null));
    }
    
    // Other endpoints...
}

Security Configuration

Spring Security setup:

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
                .csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .authorizeExchange()
                .pathMatchers("/api/v1/public/**").permitAll()
                .pathMatchers("/api/v1/auth/**").permitAll()
                .anyExchange().authenticated()
                .and()
                .addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
                .build();
    }
    
    // Other beans...
}

Query Execution

Action execution is handled in a structured way:

@Service
@RequiredArgsConstructor
public class ActionExecutionServiceImpl implements ActionExecutionService {
    private final ActionExecutorFactory executorFactory;
    
    @Override
    public Mono<ActionExecutionResult> executeAction(ActionDTO action) {
        ActionExecutor executor = executorFactory.getExecutor(action.getPluginType());
        
        if (executor == null) {
            return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_PLUGIN_ACTION));
        }
        
        return executor.execute(action);
    }
}

Advanced Patterns

Code Splitting

// Lazy loading of components
const ApplicationPage = React.lazy(() => import("pages/Applications"));
const EditorPage = React.lazy(() => import("pages/Editor"));

// Router setup
const routes = [
  {
    path: "/applications",
    component: ApplicationPage,
  },
  {
    path: "/app/editor/:applicationId/:pageId",
    component: EditorPage,
  },
  // ...
];

Plugin System

The plugin system allows extensibility:

public interface PluginExecutor<T, U> {
    Mono<ActionExecutionResult> execute(T connection, U datasourceConfiguration, Object executeActionDTO);
    Mono<T> datasourceCreate(U datasourceConfiguration);
    void datasourceDestroy(T connection);
    Set<String> getHintMessages(U datasourceConfiguration);
    // ...
}

Reactive Caching

@Service
public class CacheableRepositoryHelper {
    private final Map<String, Cache<String, Object>> cacheMap = new ConcurrentHashMap<>();
    
    public <T> Mono<T> fetchFromCache(String cacheName, String key, Supplier<Mono<T>> fetchFunction) {
        Cache<String, Object> cache = cacheMap.computeIfAbsent(cacheName, k -> 
                Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build());
        
        Object cachedValue = cache.getIfPresent(key);
        if (cachedValue != null) {
            return Mono.just((T) cachedValue);
        }
        
        return fetchFunction.get()
                .doOnNext(value -> cache.put(key, value));
    }
}

Action Collection System

Actions are grouped into collections for better organization:

@Document
public class ActionCollection {
    @Id
    private String id;
    private String name;
    private String applicationId;
    private String organizationId;
    private String pageId;
    private List<ActionDTO> actions;
    private List<String> actionIds;
    private String body;
    // ...
}

Common Code Patterns

Error Handling

Frontend error handling:

// Global error boundary
export class AppErrorBoundary extends React.Component<{}, { hasError: boolean }> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    logError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <ErrorPage />;
    }
    return this.props.children;
  }
}

Backend error handling:

@ExceptionHandler(AppsmithException.class)
public Mono<ResponseEntity<ResponseDTO<Object>>> handleAppsmithException(AppsmithException exception) {
    log.error("Application error: {}", exception.getMessage(), exception);
    
    ResponseDTO<Object> response = new ResponseDTO<>(
            exception.getHttpStatus().value(),
            null,
            new ErrorDTO(exception.getAppErrorCode(), exception.getMessage())
    );
    
    return Mono.just(ResponseEntity
            .status(exception.getHttpStatus())
            .body(response));
}

Validation

Frontend validation:

const validateWidgetName = (widgetName: string) => {
  const nameRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
  
  if (!nameRegex.test(widgetName)) {
    return "Widget name must start with a letter and can contain only letters, numbers, and underscore";
  }
  
  if (widgetName.length > 30) {
    return "Widget name must be less than 30 characters";
  }
  
  return undefined;
};

Backend validation:

@Validated
@Service
public class UserServiceImpl implements UserService {
    @Override
    public Mono<User> create(@Valid UserDTO userDTO) {
        // Implementation
    }
}

public class UserDTO {
    @NotBlank(message = "Email is mandatory")
    @Email(message = "Invalid email format")
    private String email;
    
    @NotBlank(message = "Password is mandatory")
    @Size(min = 8, message = "Password must be at least 8 characters")
    private String password;
    
    // Other fields...
}

Internationalization

// i18n setup
const i18n = createI18n({
  locale: getBrowserLocale(),
  messages: {
    en: enMessages,
    fr: frMessages,
    // Other languages...
  },
});

// Usage in components
const MyComponent = () => {
  const { t } = useTranslation();
  
  return (
    <div>
      <h1>{t('welcome.title')}</h1>
      <p>{t('welcome.message')}</p>
    </div>
  );
};

Enterprise-specific Features

Audit Logging

@Service
@ConditionalOnProperty(prefix = "appsmith", name = "audit.enabled", havingValue = "true")
public class AuditServiceImpl implements AuditService {
    private final AuditRepository repository;
    
    @Override
    public Mono<AuditLog> log(String action, String resourceId, String resourceType, User user) {
        AuditLog log = new AuditLog();
        log.setAction(action);
        log.setResourceId(resourceId);
        log.setResourceType(resourceType);
        log.setUserId(user.getId());
        log.setUsername(user.getUsername());
        log.setTimestamp(Instant.now());
        
        return repository.save(log);
    }
}

Role-Based Access Control

@Service
public class PermissionServiceImpl implements PermissionService {
    
    @Override
    public Mono<Boolean> hasPermission(User user, String resourceId, PermissionType permission) {
        return userGroupRepository.findByUserIdAndOrganizationId(user.getId(), user.getCurrentOrganizationId())
                .flatMap(userGroup -> {
                    if (userGroup.getRole() == UserRole.ORGANIZATION_ADMIN) {
                        return Mono.just(true);
                    }
                    
                    return resourcePermissionRepository
                            .findByResourceIdAndPermission(resourceId, permission)
                            .any(resourcePermission -> resourcePermission.getUserGroupId().equals(userGroup.getId()));
                });
    }
}

SSO Integration

@Configuration
@ConditionalOnProperty(prefix = "appsmith.oauth2", name = "enabled", havingValue = "true")
public class OAuth2Config {
    
    @Bean
    public ReactiveClientRegistrationRepository clientRegistrationRepository() {
        List<ClientRegistration> registrations = new ArrayList<>();
        
        registrations.add(googleClientRegistration());
        registrations.add(githubClientRegistration());
        // Other providers...
        
        return new InMemoryReactiveClientRegistrationRepository(registrations);
    }
    
    private ClientRegistration googleClientRegistration() {
        return ClientRegistration.withRegistrationId("google")
                .clientId(googleClientId)
                .clientSecret(googleClientSecret)
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("{baseUrl}/api/v1/oauth2/callback/{registrationId}")
                .scope("openid", "email", "profile")
                .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
                .tokenUri("https://www.googleapis.com/oauth2/v4/token")
                .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
                .userNameAttributeName(IdTokenClaimNames.SUB)
                .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
                .clientName("Google")
                .build();
    }
}

Performance Optimization Techniques

Frontend Optimizations

  1. Memoization:
const MemoizedComponent = React.memo(MyComponent);

// Using useMemo for computed values
const computedValue = useMemo(() => {
  return expensiveComputation(a, b);
}, [a, b]);

// Using useCallback for functions
const handleClick = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
  1. Virtualization for Large Lists:
import { FixedSizeList } from 'react-window';

const MyList = ({ items }) => (
  <FixedSizeList
    height={500}
    width={500}
    itemCount={items.length}
    itemSize={50}
  >
    {({ index, style }) => (
      <div style={style}>
        {items[index].name}
      </div>
    )}
  </FixedSizeList>
);

Backend Optimizations

  1. Batch Processing:
@Service
public class BatchImportServiceImpl implements BatchImportService {
    @Override
    public Mono<ImportResult> importEntities(List<Entity> entities) {
        return Flux.fromIterable(entities)
                .flatMap(this::validateEntity)
                .collectList()
                .flatMap(validatedEntities -> 
                    repository.saveAll(validatedEntities)
                        .collectList()
                        .map(savedEntities -> new ImportResult(savedEntities.size(), null))
                );
    }
}
  1. Query Optimization:
// Using MongoDB indexes
@Document
public class Application {
    // ...
    
    @Indexed
    private String name;
    
    @Indexed
    private String organizationId;
    
    @CompoundIndex(def = "{'organizationId': 1, 'name': 1}", unique = true)
    // ...
}

This document should provide Cursor with a deeper understanding of the technical implementation details of Appsmith, allowing for more accurate and contextual assistance when working with the codebase.