Add READMEs for major folders

This commit is contained in:
bymyself
2025-05-17 22:40:24 -07:00
parent e76e9ec61a
commit 4804f6d62f
4 changed files with 1062 additions and 0 deletions

310
src/composables/README.md Normal file
View File

@@ -0,0 +1,310 @@
# Composables
This directory contains Vue composables for the ComfyUI frontend application. Composables are reusable pieces of logic that encapsulate stateful functionality and can be shared across components.
## Table of Contents
- [Overview](#overview)
- [Composable Architecture](#composable-architecture)
- [Composable Categories](#composable-categories)
- [Usage Guidelines](#usage-guidelines)
- [VueUse Library](#vueuse-library)
- [Development Guidelines](#development-guidelines)
- [Common Patterns](#common-patterns)
## Overview
Vue composables are a core part of Vue 3's Composition API and provide a way to extract and reuse stateful logic between multiple components. In ComfyUI, composables are used to encapsulate behaviors like:
- State management
- DOM interactions
- Feature-specific functionality
- UI behaviors
- Data fetching
Composables enable a more modular and functional approach to building components, allowing for better code reuse and separation of concerns. They help keep your component code cleaner by extracting complex logic into separate, reusable functions.
As described in the [Vue.js documentation](https://vuejs.org/guide/reusability/composables.html), composables are:
> Functions that leverage Vue's Composition API to encapsulate and reuse stateful logic.
## Composable Architecture
The composable architecture in ComfyUI follows these principles:
1. **Single Responsibility**: Each composable should focus on a specific concern
2. **Composition**: Composables can use other composables
3. **Reactivity**: Composables leverage Vue's reactivity system
4. **Reusability**: Composables are designed to be used across multiple components
The following diagram shows how composables fit into the application architecture:
```
┌─────────────────────────────────────────────────────────┐
│ Vue Components │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Component A │ │ Component B │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
└────────────┼───────────────────┼────────────────────────┘
│ │
▼ ▼
┌────────────┴───────────────────┴────────────────────────┐
│ Composables │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ useFeatureA │ │ useFeatureB │ │ useFeatureC │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
└─────────┼────────────────┼────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌─────────┴────────────────┴────────────────┴─────────────┐
│ Services & Stores │
└─────────────────────────────────────────────────────────┘
```
## Composable Categories
ComfyUI's composables are organized into several categories:
### Auth
Composables for authentication and user management:
- `useCurrentUser` - Provides access to the current user information
- `useFirebaseAuthActions` - Handles Firebase authentication operations
### Element
Composables for DOM and element interactions:
- `useAbsolutePosition` - Handles element positioning
- `useDomClipping` - Manages clipping of DOM elements
- `useResponsiveCollapse` - Manages responsive collapsing of elements
### Node
Composables for node-specific functionality:
- `useNodeBadge` - Handles node badge display and interaction
- `useNodeImage` - Manages node image preview
- `useNodeDragAndDrop` - Handles drag and drop for nodes
- `useNodeChatHistory` - Manages chat history for nodes
### Settings
Composables for settings management:
- `useSettingSearch` - Provides search functionality for settings
- `useSettingUI` - Manages settings UI interactions
### Sidebar
Composables for sidebar functionality:
- `useNodeLibrarySidebarTab` - Manages the node library sidebar tab
- `useQueueSidebarTab` - Manages the queue sidebar tab
- `useWorkflowsSidebarTab` - Manages the workflows sidebar tab
### Widgets
Composables for widget functionality:
- `useBooleanWidget` - Manages boolean widget interactions
- `useComboWidget` - Manages combo box widget interactions
- `useFloatWidget` - Manages float input widget interactions
- `useImagePreviewWidget` - Manages image preview widget
## Usage Guidelines
When using composables in components, follow these guidelines:
1. **Import and call** composables at the top level of the `setup` function
2. **Destructure returned values** to use in your component
3. **Respect reactivity** by not destructuring reactive objects
4. **Handle cleanup** by using `onUnmounted` when necessary
5. **Use VueUse** for common functionality instead of writing from scratch
Example usage:
```vue
<template>
<div
:class="{ 'dragging': isDragging }"
@mousedown="startDrag"
@mouseup="endDrag"
>
<img v-if="imageUrl" :src="imageUrl" alt="Node preview" />
</div>
</template>
<script setup lang="ts">
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop';
import { useNodeImage } from '@/composables/node/useNodeImage';
// Use composables at the top level
const { isDragging, startDrag, endDrag } = useNodeDragAndDrop();
const { imageUrl, loadImage } = useNodeImage();
// Use returned values in your component
</script>
```
## VueUse Library
ComfyUI leverages the [VueUse](https://vueuse.org/) library, which provides a collection of essential Vue Composition API utilities. Instead of implementing common functionality from scratch, prefer using VueUse composables for:
- DOM event handling (`useEventListener`, `useMouseInElement`)
- Element measurements (`useElementBounding`, `useElementSize`)
- Asynchronous operations (`useAsyncState`, `useFetch`)
- Animation and timing (`useTransition`, `useTimeout`, `useInterval`)
- Browser APIs (`useLocalStorage`, `useClipboard`)
- Sensors (`useDeviceMotion`, `useDeviceOrientation`)
- State management (`createGlobalState`, `useStorage`)
- ...and [more](https://vueuse.org/functions.html)
Examples:
```js
// Instead of manually adding/removing event listeners
import { useEventListener } from '@vueuse/core'
useEventListener(window, 'resize', handleResize)
// Instead of manually tracking element measurements
import { useElementBounding } from '@vueuse/core'
const { width, height, top, left } = useElementBounding(elementRef)
// Instead of manual async state management
import { useAsyncState } from '@vueuse/core'
const { state, isReady, isLoading } = useAsyncState(
fetch('https://api.example.com/data').then(r => r.json()),
{ data: [] }
)
```
For a complete list of available functions, see the [VueUse documentation](https://vueuse.org/functions.html).
## Development Guidelines
When creating or modifying composables, follow these best practices:
1. **Name with `use` prefix**: All composables should start with "use"
2. **Return an object**: Composables should return an object with named properties/methods
3. **Handle cleanup**: Use `onUnmounted` to clean up resources
4. **Document parameters and return values**: Add JSDoc comments
5. **Test composables**: Write unit tests for composable functionality
6. **Use VueUse**: Leverage VueUse composables instead of reimplementing common functionality
7. **Implement proper cleanup**: Cancel debounced functions, pending requests, and clear maps
8. **Use watchDebounced/watchThrottled**: For performance-sensitive reactive operations
### Composable Template
Here's a template for creating a new composable:
```typescript
import { ref, computed, onMounted, onUnmounted } from 'vue';
/**
* Composable for [functionality description]
* @param options Configuration options
* @returns Object containing state and methods
*/
export function useExample(options = {}) {
// State
const state = ref({
// Initial state
});
// Computed values
const derivedValue = computed(() => {
// Compute from state
return state.value.someProperty;
});
// Methods
function doSomething() {
// Implementation
}
// Lifecycle hooks
onMounted(() => {
// Setup
});
onUnmounted(() => {
// Cleanup
});
// Return exposed state and methods
return {
state,
derivedValue,
doSomething
};
}
```
## Common Patterns
Composables in ComfyUI frequently use these patterns:
### State Management
```typescript
export function useState() {
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment
};
}
```
### Event Handling with VueUse
```typescript
import { useEventListener } from '@vueuse/core';
export function useKeyPress(key) {
const isPressed = ref(false);
useEventListener('keydown', (e) => {
if (e.key === key) {
isPressed.value = true;
}
});
useEventListener('keyup', (e) => {
if (e.key === key) {
isPressed.value = false;
}
});
return { isPressed };
}
```
### Fetch & Load with VueUse
```typescript
import { useAsyncState } from '@vueuse/core';
export function useFetchData(url) {
const { state: data, isLoading, error, execute: refresh } = useAsyncState(
async () => {
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch data');
return response.json();
},
null,
{ immediate: true }
);
return { data, isLoading, error, refresh };
}
```
For more information on Vue composables, refer to the [Vue.js Composition API documentation](https://vuejs.org/guide/reusability/composables.html) and the [VueUse documentation](https://vueuse.org/).

View File

@@ -0,0 +1,139 @@
# Core Extensions
This directory contains the core extensions that provide essential functionality to the ComfyUI frontend.
## Table of Contents
- [Overview](#overview)
- [Extension Architecture](#extension-architecture)
- [Core Extensions](#core-extensions)
- [Extension Development](#extension-development)
- [Extension Hooks](#extension-hooks)
- [Further Reading](#further-reading)
## Overview
Extensions in ComfyUI are modular JavaScript modules that extend and enhance the functionality of the frontend. The extensions in this directory are considered "core" as they provide fundamental features that are built into ComfyUI by default.
## Extension Architecture
ComfyUI's extension system follows these key principles:
1. **Registration-based:** Extensions must register themselves with the application using `app.registerExtension()`
2. **Hook-driven:** Extensions interact with the system through predefined hooks
3. **Non-intrusive:** Extensions should avoid directly modifying core objects where possible
## Core Extensions List
The core extensions include:
| Extension | Description |
|-----------|-------------|
| clipspace.ts | Implements the Clipspace feature for temporary image storage |
| dynamicPrompts.ts | Provides dynamic prompt generation capabilities |
| groupNode.ts | Implements the group node functionality to organize workflows |
| load3d.ts | Supports 3D model loading and visualization |
| maskeditor.ts | Implements the mask editor for image masking operations |
| noteNode.ts | Adds note nodes for documentation within workflows |
| rerouteNode.ts | Implements reroute nodes for cleaner workflow connections |
| uploadImage.ts | Handles image upload functionality |
| webcamCapture.ts | Provides webcam capture capabilities |
| widgetInputs.ts | Implements various widget input types |
## Extension Development
When developing or modifying extensions, follow these best practices:
1. **Use provided hooks** rather than directly modifying core application objects
2. **Maintain compatibility** with other extensions
3. **Follow naming conventions** for both extension names and settings
4. **Properly document** extension hooks and functionality
5. **Test with other extensions** to ensure no conflicts
### Extension Registration
Extensions are registered using the `app.registerExtension()` method:
```javascript
app.registerExtension({
name: "MyExtension",
// Hook implementations
async init() {
// Implementation
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// Implementation
}
// Other hooks as needed
});
```
## Extension Hooks
ComfyUI extensions can implement various hooks that are called at specific points in the application lifecycle:
### Hook Execution Sequence
#### Web Page Load
```
init
addCustomNodeDefs
getCustomWidgets
beforeRegisterNodeDef [repeated multiple times]
registerCustomNodes
beforeConfigureGraph
nodeCreated
loadedGraphNode
afterConfigureGraph
setup
```
#### Loading Workflow
```
beforeConfigureGraph
beforeRegisterNodeDef [zero, one, or multiple times]
nodeCreated [repeated multiple times]
loadedGraphNode [repeated multiple times]
afterConfigureGraph
```
#### Adding New Node
```
nodeCreated
```
### Key Hooks
| Hook | Description |
|------|-------------|
| `init` | Called after canvas creation but before nodes are added |
| `setup` | Called after the application is fully set up and running |
| `addCustomNodeDefs` | Called before nodes are registered with the graph |
| `getCustomWidgets` | Allows extensions to add custom widgets |
| `beforeRegisterNodeDef` | Allows extensions to modify nodes before registration |
| `registerCustomNodes` | Allows extensions to register additional nodes |
| `loadedGraphNode` | Called when a node is reloaded onto the graph |
| `nodeCreated` | Called after a node's constructor |
| `beforeConfigureGraph` | Called before a graph is configured |
| `afterConfigureGraph` | Called after a graph is configured |
| `getSelectionToolboxCommands` | Allows extensions to add commands to the selection toolbox |
For the complete list of available hooks and detailed descriptions, see the [ComfyExtension interface in comfy.ts](https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/src/types/comfy.ts).
## Further Reading
For more detailed information about ComfyUI's extension system, refer to the official documentation:
- [JavaScript Extension Overview](https://docs.comfy.org/custom-nodes/js/javascript_overview)
- [JavaScript Hooks](https://docs.comfy.org/custom-nodes/js/javascript_hooks)
- [JavaScript Objects and Hijacking](https://docs.comfy.org/custom-nodes/js/javascript_objects_and_hijacking)
- [JavaScript Settings](https://docs.comfy.org/custom-nodes/js/javascript_settings)
- [JavaScript Examples](https://docs.comfy.org/custom-nodes/js/javascript_examples)
Also, check the main [README.md](https://github.com/Comfy-Org/ComfyUI_frontend#developer-apis) section on Developer APIs for the latest information on extension APIs and features.

257
src/services/README.md Normal file
View File

@@ -0,0 +1,257 @@
# Services
This directory contains the service layer for the ComfyUI frontend application. Services encapsulate application logic and functionality into organized, reusable modules.
## Table of Contents
- [Overview](#overview)
- [Service Architecture](#service-architecture)
- [Core Services](#core-services)
- [Service Development Guidelines](#service-development-guidelines)
- [Common Design Patterns](#common-design-patterns)
## Overview
Services in ComfyUI provide organized modules that implement the application's functionality and logic. They handle operations such as API communication, workflow management, user settings, and other essential features.
The term "business logic" in this context refers to the code that implements the core functionality and behavior of the application - the rules, processes, and operations that make ComfyUI work as expected, separate from the UI display code.
Services help organize related functionality into cohesive units, making the codebase more maintainable and testable. By centralizing related operations in services, the application achieves better separation of concerns, with UI components focusing on presentation and services handling functional operations.
## Service Architecture
The service layer in ComfyUI follows these architectural principles:
1. **Domain-driven**: Each service focuses on a specific domain of the application
2. **Stateless when possible**: Services generally avoid maintaining internal state
3. **Reusable**: Services can be used across multiple components
4. **Testable**: Services are designed for easy unit testing
5. **Isolated**: Services have clear boundaries and dependencies
While services can interact with both UI components and stores (centralized state), they primarily focus on implementing functionality rather than managing state. The following diagram illustrates how services fit into the application architecture:
```
┌─────────────────────────────────────────────────────────┐
│ UI Components │
└────────────────────────────┬────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Composables │
└────────────────────────────┬────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Services │
│ │
│ (Application Functionality) │
└────────────────────────────┬────────────────────────────┘
┌───────────┴───────────┐
▼ ▼
┌───────────────────────────┐ ┌─────────────────────────┐
│ Stores │ │ External APIs │
│ (Centralized State) │ │ │
└───────────────────────────┘ └─────────────────────────┘
```
## Core Services
The core services include:
| Service | Description |
|---------|-------------|
| algoliaSearchService.ts | Implements search functionality using Algolia |
| autoQueueService.ts | Manages automatic queue execution |
| colorPaletteService.ts | Handles color palette management and customization |
| comfyManagerService.ts | Manages ComfyUI application packages and updates |
| comfyRegistryService.ts | Handles registration and discovery of ComfyUI extensions |
| dialogService.ts | Provides dialog and modal management |
| extensionService.ts | Manages extension registration and lifecycle |
| keybindingService.ts | Handles keyboard shortcuts and keybindings |
| litegraphService.ts | Provides utilities for working with the LiteGraph library |
| load3dService.ts | Manages 3D model loading and visualization |
| nodeSearchService.ts | Implements node search functionality |
| workflowService.ts | Handles workflow operations (save, load, execute) |
## Service Development Guidelines
In ComfyUI, services can be implemented using two approaches:
### 1. Class-based Services
For complex services with state management and multiple methods, class-based services are used:
```typescript
export class NodeSearchService {
// Service state
private readonly nodeFuseSearch: FuseSearch<ComfyNodeDefImpl>
private readonly filters: Record<string, FuseFilter<ComfyNodeDefImpl, string>>
constructor(data: ComfyNodeDefImpl[]) {
// Initialize state
this.nodeFuseSearch = new FuseSearch(data, { /* options */ })
// Setup filters
this.filters = {
inputType: new FuseFilter<ComfyNodeDefImpl, string>(/* options */),
category: new FuseFilter<ComfyNodeDefImpl, string>(/* options */)
}
}
public searchNode(query: string, filters: FuseFilterWithValue[] = []): ComfyNodeDefImpl[] {
// Implementation
return results
}
}
```
### 2. Composable-style Services
For simpler services or those that need to integrate with Vue's reactivity system, we prefer using composable-style services:
```typescript
export function useNodeSearchService(initialData: ComfyNodeDefImpl[]) {
// State (reactive if needed)
const data = ref(initialData)
// Search functionality
function searchNodes(query: string) {
// Implementation
return results
}
// Additional methods
function refreshData(newData: ComfyNodeDefImpl[]) {
data.value = newData
}
// Return public API
return {
searchNodes,
refreshData
}
}
```
When deciding between these approaches, consider:
1. **Stateful vs. Stateless**: For stateful services, classes often provide clearer encapsulation
2. **Reactivity needs**: If the service needs to be reactive, composable-style services integrate better with Vue's reactivity system
3. **Complexity**: For complex services with many methods and internal state, classes can provide better organization
4. **Testing**: Both approaches can be tested effectively, but composables may be simpler to test with Vue Test Utils
### Service Template
Here's a template for creating a new composable-style service:
```typescript
/**
* Service for managing [domain/functionality]
*/
export function useExampleService() {
// Private state/functionality
const cache = new Map()
/**
* Description of what this method does
* @param param1 Description of parameter
* @returns Description of return value
*/
async function performOperation(param1: string) {
try {
// Implementation
return result
} catch (error) {
// Error handling
console.error(`Operation failed: ${error.message}`)
throw error
}
}
// Return public API
return {
performOperation
}
}
```
## Common Design Patterns
Services in ComfyUI frequently use the following design patterns:
### Caching and Request Deduplication
```typescript
export function useCachedService() {
const cache = new Map()
const pendingRequests = new Map()
async function fetchData(key: string) {
// Check cache first
if (cache.has(key)) return cache.get(key)
// Check if request is already in progress
if (pendingRequests.has(key)) {
return pendingRequests.get(key)
}
// Perform new request
const requestPromise = fetch(`/api/${key}`)
.then(response => response.json())
.then(data => {
cache.set(key, data)
pendingRequests.delete(key)
return data
})
pendingRequests.set(key, requestPromise)
return requestPromise
}
return { fetchData }
}
```
### Factory Pattern
```typescript
export function useNodeFactory() {
function createNode(type: string, config: Record<string, any>) {
// Create node based on type and configuration
switch (type) {
case 'basic':
return { /* basic node implementation */ }
case 'complex':
return { /* complex node implementation */ }
default:
throw new Error(`Unknown node type: ${type}`)
}
}
return { createNode }
}
```
### Facade Pattern
```typescript
export function useWorkflowService(
apiService,
graphService,
storageService
) {
// Provides a simple interface to complex subsystems
async function saveWorkflow(name: string) {
const graphData = graphService.serializeGraph()
const storagePath = await storageService.getPath(name)
return apiService.saveData(storagePath, graphData)
}
return { saveWorkflow }
}
```
For more detailed information about the service layer pattern and its applications, refer to:
- [Service Layer Pattern](https://en.wikipedia.org/wiki/Service_layer_pattern)
- [Service-Orientation](https://en.wikipedia.org/wiki/Service-orientation)

356
src/stores/README.md Normal file
View File

@@ -0,0 +1,356 @@
# Stores
This directory contains Pinia stores for the ComfyUI frontend application. Stores provide centralized state management for the application.
## Table of Contents
- [Overview](#overview)
- [Store Architecture](#store-architecture)
- [Core Stores](#core-stores)
- [Store Development Guidelines](#store-development-guidelines)
- [Common Patterns](#common-patterns)
- [Testing Stores](#testing-stores)
## Overview
Stores in ComfyUI use [Pinia](https://pinia.vuejs.org/), Vue's official state management library. Each store is responsible for managing a specific domain of the application state, such as user data, workflow information, graph state, and UI configuration.
Stores provide a way to maintain global application state that can be accessed from any component, regardless of where those components are in the component hierarchy. This solves the problem of "prop drilling" (passing data down through multiple levels of components) and allows components that aren't directly related to share and modify the same state.
For example, without global state:
```
App
┌──────────┴──────────┐
│ │
HeaderBar Canvas
│ │
│ │
UserMenu NodeProperties
```
In this structure, if the `UserMenu` component needs to update something that affects `NodeProperties`, the data would need to be passed up to `App` and then down again, through all intermediate components.
With Pinia stores, components can directly access and update the shared state:
```
┌─────────────────┐
│ │
│ Pinia Stores │
│ │
└───────┬─────────┘
│ Accessed by
┌──────────────────────────┐
│ │
│ Components │
│ │
└──────────────────────────┘
```
## Store Architecture
The store architecture in ComfyUI follows these principles:
1. **Domain-driven**: Each store focuses on a specific domain of the application
2. **Single source of truth**: Stores serve as the definitive source for specific data
3. **Composition**: Stores can interact with other stores when needed
4. **Actions for logic**: Business logic is encapsulated in store actions
5. **Getters for derived state**: Computed values are exposed via getters
The following diagram illustrates the store architecture and data flow:
```
┌─────────────────────────────────────────────────────────┐
│ Vue Components │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Component A │ │ Component B │ │
│ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │
└───────────┼────────────────────────────┼────────────────┘
│ │
│ ┌───────────────┐ │
└────►│ Composables │◄─────┘
└───────┬───────┘
┌─────────────────────────┼─────────────────────────────┐
│ Pinia Stores │ │
│ │ │
│ ┌───────────────────▼───────────────────────┐ │
│ │ Actions │ │
│ └───────────────────┬───────────────────────┘ │
│ │ │
│ ┌───────────────────▼───────────────────────┐ │
│ │ State │ │
│ └───────────────────┬───────────────────────┘ │
│ │ │
│ ┌───────────────────▼───────────────────────┐ │
│ │ Getters │ │
│ └───────────────────┬───────────────────────┘ │
│ │ │
└─────────────────────────┼─────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ External Services │
│ (API, localStorage, WebSocket, etc.) │
└─────────────────────────────────────────────────────────┘
```
## Core Stores
The core stores include:
| Store | Description |
|-------|-------------|
| aboutPanelStore.ts | Manages the About panel state and badges |
| apiKeyAuthStore.ts | Handles API key authentication |
| comfyManagerStore.ts | Manages ComfyUI application state |
| comfyRegistryStore.ts | Handles extensions registry |
| commandStore.ts | Manages commands and command execution |
| dialogStore.ts | Controls dialog/modal display and state |
| domWidgetStore.ts | Manages DOM widget state |
| executionStore.ts | Tracks workflow execution state |
| extensionStore.ts | Manages extension registration and state |
| firebaseAuthStore.ts | Handles Firebase authentication |
| graphStore.ts | Manages the graph canvas state |
| imagePreviewStore.ts | Controls image preview functionality |
| keybindingStore.ts | Manages keyboard shortcuts |
| menuItemStore.ts | Handles menu items and their state |
| modelStore.ts | Manages AI models information |
| nodeDefStore.ts | Manages node definitions |
| queueStore.ts | Handles the execution queue |
| settingStore.ts | Manages application settings |
| userStore.ts | Manages user data and preferences |
| workflowStore.ts | Handles workflow data and operations |
| workspace/* | Stores related to the workspace UI |
## Store Development Guidelines
When developing or modifying stores, follow these best practices:
1. **Define clear purpose**: Each store should have a specific responsibility
2. **Use actions for async operations**: Encapsulate asynchronous logic in actions
3. **Keep stores focused**: Each store should manage related state
4. **Document public API**: Add comments for state properties, actions, and getters
5. **Use getters for derived state**: Compute derived values using getters
6. **Test store functionality**: Write unit tests for stores
### Store Template
Here's a template for creating a new Pinia store, following the setup style used in ComfyUI:
```typescript
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useExampleStore = defineStore('example', () => {
// State
const items = ref([])
const isLoading = ref(false)
const error = ref(null)
// Getters
const itemCount = computed(() => items.value.length)
const hasError = computed(() => error.value !== null)
// Actions
function addItem(item) {
items.value.push(item)
}
async function fetchItems() {
isLoading.value = true
error.value = null
try {
const response = await fetch('/api/items')
const data = await response.json()
items.value = data
} catch (err) {
error.value = err.message
} finally {
isLoading.value = false
}
}
// Expose state, getters, and actions
return {
// State
items,
isLoading,
error,
// Getters
itemCount,
hasError,
// Actions
addItem,
fetchItems
}
})
```
## Common Patterns
Stores in ComfyUI frequently use these patterns:
### API Integration
```typescript
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { api } from '@/scripts/api'
export const useDataStore = defineStore('data', () => {
const data = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchData() {
loading.value = true
try {
const result = await api.getData()
data.value = result
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return {
data,
loading,
error,
fetchData
}
})
```
### Store Composition
```typescript
import { defineStore, storeToRefs } from 'pinia'
import { computed, ref, watch } from 'vue'
import { useOtherStore } from './otherStore'
export const useComposedStore = defineStore('composed', () => {
const otherStore = useOtherStore()
const { someData } = storeToRefs(otherStore)
// Local state
const localState = ref(0)
// Computed value based on other store
const derivedValue = computed(() => {
return computeFromOtherData(someData.value, localState.value)
})
// Action that uses another store
async function complexAction() {
await otherStore.someAction()
localState.value += 1
}
return {
localState,
derivedValue,
complexAction
}
})
```
### Persistent State
```typescript
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
export const usePreferencesStore = defineStore('preferences', () => {
// Load from localStorage if available
const theme = ref(localStorage.getItem('theme') || 'light')
const fontSize = ref(parseInt(localStorage.getItem('fontSize') || '14'))
// Save to localStorage when changed
watch(theme, (newTheme) => {
localStorage.setItem('theme', newTheme)
})
watch(fontSize, (newSize) => {
localStorage.setItem('fontSize', newSize.toString())
})
function setTheme(newTheme) {
theme.value = newTheme
}
return {
theme,
fontSize,
setTheme
}
})
```
## Testing Stores
Stores should be tested to ensure they behave as expected. Here's an example of how to test a store:
```typescript
import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { api } from '@/scripts/api'
import { useExampleStore } from '@/stores/exampleStore'
// Mock API dependencies
vi.mock('@/scripts/api', () => ({
api: {
getData: vi.fn()
}
}))
describe('useExampleStore', () => {
let store: ReturnType<typeof useExampleStore>
beforeEach(() => {
// Create a fresh pinia instance and make it active
setActivePinia(createPinia())
store = useExampleStore()
// Clear all mocks
vi.clearAllMocks()
})
it('should initialize with default state', () => {
expect(store.items).toEqual([])
expect(store.isLoading).toBe(false)
expect(store.error).toBeNull()
})
it('should add an item', () => {
store.addItem('test')
expect(store.items).toEqual(['test'])
expect(store.itemCount).toBe(1)
})
it('should fetch items', async () => {
// Setup mock response
vi.mocked(api.getData).mockResolvedValue(['item1', 'item2'])
// Call the action
await store.fetchItems()
// Verify state changes
expect(store.isLoading).toBe(false)
expect(store.items).toEqual(['item1', 'item2'])
expect(store.error).toBeNull()
})
})
```
For more information on Pinia, refer to the [Pinia documentation](https://pinia.vuejs.org/introduction.html).