Storage Layer Reference
The OpenClaw WebOS uses a PostgreSQL-backed storage layer that provides CRUD operations for projects, tasks, workflows, dependencies, and all related entities.
Source: storage/asana.js
Architecture
The storage layer is implemented as a single module (storage/asana.js) that exports an AsanaStorage class. It wraps a PostgreSQL connection pool and provides methods for all dashboard data operations.
Connection
const storage = new AsanaStorage({
host: 'localhost',
port: 5432,
database: 'openclaw_dashboard',
user: 'openclaw',
password: 'change-me'
});
Connection parameters are read from environment variables (see configuration-reference.md).
Entity Operations
Projects
| Method | Description |
|---|---|
listProjects(options) |
List projects with pagination, filtering by status/tags |
getProject(id) |
Get a single project by ID |
createProject(data) |
Create a new project |
updateProject(id, data) |
Update project fields |
deleteProject(id) |
Delete a project (cascade deletes tasks) |
getProjectByNamespace(namespace) |
Find project by QMD namespace |
Options for listProjects:
status— Filter by status (active,paused,archived)tags— Filter by tags (array)limit— Page size (max 200)offset— Pagination offsetsort— Sort field (name,created_at,updated_at)order— Sort direction (asc,desc)
Tasks
| Method | Description |
|---|---|
listTasks(options) |
List tasks with filtering, pagination, and search |
getTask(id) |
Get a single task with metadata |
createTask(data) |
Create a new task |
updateTask(id, data) |
Update task fields |
deleteTask(id) |
Delete a task |
batchUpdateTasks(updates) |
Update multiple tasks in a transaction |
getTasksByProject(projectId, options) |
List tasks for a project |
getTasksByStatus(status, options) |
List tasks by status |
getTasksByOwner(owner, options) |
List tasks assigned to an owner |
getTaskGraph(projectId) |
Get task dependency graph for a project |
Task options:
project_id— Filter by projectstatus— Filter by statuspriority— Filter by priorityowner— Filter by ownerlabels— Filter by labelssearch— Full-text search in title and descriptionsort— Sort field (newest,oldest,updated,alpha,priority,due_date)limit/offset— Paginationupdated_since— Incremental sync (only tasks modified after timestamp)
Workflows
| Method | Description |
|---|---|
listWorkflows(options) |
List all workflows |
getWorkflow(id) |
Get a workflow by ID |
createWorkflow(data) |
Create a new workflow |
updateWorkflow(id, data) |
Update a workflow |
deleteWorkflow(id) |
Delete a workflow |
getDefaultWorkflow(projectId) |
Get default workflow for a project |
Audit Log
| Method | Description |
|---|---|
logAudit(taskId, actor, action, oldValue, newValue) |
Create an audit log entry |
getAuditLog(options) |
Query audit log with filters |
getAuditForTask(taskId) |
Get all audit entries for a task |
Audit log options:
task_id— Filter by taskactor— Filter by actoraction— Filter by action typefrom/to— Date range filterlimit/offset— Pagination
Default Workflow States
const DEFAULT_WORKFLOW_STATES = [
'backlog',
'ready',
'in_progress',
'blocked',
'review',
'completed',
'archived'
];
Valid Values
Task Priorities
const PRIORITIES = ['low', 'medium', 'high', 'critical'];
Project Statuses
const PROJECT_STATUSES = ['active', 'paused', 'archived'];
Dependency Management
The storage layer automatically handles dependency-based blocking:
- When a task has
dependency_idspointing to incomplete tasks, its status may be auto-blocked - When all dependencies complete, blocked tasks can be auto-unblocked
- Dependency block events are recorded in the audit log with action
auto_block_unmet_dependencies
buildDependencyBlockHistoryEntry(incompleteDependencies, actor)
Creates a structured audit entry for dependency-related status changes.
Fixture Detection
The storage layer includes fixture project detection to filter out test data:
const FIXTURE_PROJECT_PATTERNS = [
/^board test project\b/i,
/^test project\b/i,
/^test$/i,
/^my new project\b/i,
/\bfixture\b/i,
/\bseed\b/i
];
Projects matching these patterns are excluded from normal listings unless explicitly requested.
Security
The storage layer integrates with lib/qmd-security for input validation and sanitization. Key protections:
- SQL injection: All queries use parameterized statements via
pgPool - Input normalization: Metadata fields are validated as plain objects (no arrays)
- Project ID normalization: Empty or “all” project IDs are normalized to empty string (list all)
- Pagination limits: Maximum page size of 200 enforced
Org Bootstrap Integration
The storage layer imports bootstrap data from org-bootstrap.js:
- Departments:
DEPARTMENTS— Seed department definitions - Agent profiles:
AGENT_PROFILES— Seed agent profile definitions - Lookup helpers:
getDepartmentBySlug(),getAgentProfileById()
These are used as fallbacks when the database tables haven’t been seeded yet (before migrations are applied).
Error Handling
All storage methods return Promises and throw on database errors. The caller (task-server API layer) is responsible for:
- Catching errors
- Mapping to HTTP status codes (404, 500, etc.)
- Returning error responses to the client
Common patterns:
SELECT ... WHERE id = $1returning no rows → 404 Not FoundINSERT ... ON CONFLICT→ 409 Conflict (duplicate)- Connection errors → 503 Service Unavailable
Performance Notes
- Connection pooling: Uses
pg.Poolwith default pool size (10 connections) - Prepared statements: All queries use parameterized
$1, $2, ...placeholders - GIN indexes: Array fields (
labels,dependency_ids) and JSONB (metadata) have GIN indexes - Composite indexes: Common query patterns (project+status, owner, due_date) have dedicated indexes
- Pagination: Always use
limit/offsetfor large result sets