Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5edd132
feat: implement durable objects for editor page
leonardcser Sep 30, 2025
e9c051f
style: fix lint errors
leonardcser Sep 30, 2025
c0a3571
refactor: rename workflow-related types and update WebSocket service …
bchapuis Oct 3, 2025
b6dbccc
refactor: reorganize workflow-related type imports for better structu…
bchapuis Oct 3, 2025
eec2b93
refactor: rename WorkflowDO to DurableWorkflow and update related bin…
bchapuis Oct 3, 2025
0a27289
feat: enhance workflow state management with node drag handling and S…
bchapuis Oct 3, 2025
81d651f
style: simplify console log statement in DurableWorkflow class
bchapuis Oct 3, 2025
f8113be
refactor: streamline DurableWorkflow class by consolidating database …
bchapuis Oct 3, 2025
8c2e123
fix: update WebSocket endpoint to require workflowId as a URL paramet…
bchapuis Oct 3, 2025
580b1ba
refactor: replace state with refs for nodes and edges in EditorPage t…
bchapuis Oct 4, 2025
d03ddd7
Create CLAUDE.md
bchapuis Oct 4, 2025
f98983e
Simplify state management in durable object
bchapuis Oct 4, 2025
6d5cb3a
Refactor durable-workflow into user-session
bchapuis Oct 4, 2025
88ca664
Make the implementation more robust
bchapuis Oct 4, 2025
5346421
Rename the user session to workflow session
bchapuis Oct 4, 2025
25f2d1b
Handle update message in the frontend
bchapuis Oct 4, 2025
c096898
Refactor code for improved readability and consistency in error handling
bchapuis Oct 5, 2025
f28bcde
Remove unused debounce function from utils
bchapuis Oct 5, 2025
b2e1b28
Remove migrations section from wrangler configuration
bchapuis Oct 5, 2025
7a913f2
Make the BROWSER binding optional
bchapuis Oct 5, 2025
ca43983
Remove dependency '@cloudflare/workers-types'
bchapuis Oct 5, 2025
6d19a1a
Update wrangler dependency to version 4.42.0 in package.json and pnpm…
bchapuis Oct 5, 2025
549e199
Reorganize exports in index.ts to improve module structure
bchapuis Oct 5, 2025
4ebf987
Remove redundant script_name from WORKFLOW_SESSION bindings in wrangl…
bchapuis Oct 5, 2025
8c11255
Add migration for WorkflowSession class in wrangler configuration
bchapuis Oct 5, 2025
51d5e65
Add migration for WorkflowSession class in wrangler configuration
bchapuis Oct 5, 2025
2eeec00
Refactor workflow execution handling: remove monitorProgress flag, ad…
bchapuis Oct 6, 2025
a6984cc
refactor: restructure runtime
bchapuis Oct 6, 2025
280e764
Add unit tests for the components of the runtime
bchapuis Oct 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Dafthunk is a visual workflow automation platform built on Cloudflare infrastructure (Workers, D1, R2, AI). Users create workflows by connecting 50+ node types in a visual editor (React Flow).

**Monorepo structure** (pnpm workspaces):
- `apps/api` - Backend (Hono on Cloudflare Workers)
- `apps/web` - Frontend (React 19 + React Router v7 + Vite)
- `packages/types` - Shared TypeScript types
- `packages/utils` - Shared utilities

## Development Commands

### Common commands
```bash
pnpm dev # Start all services
pnpm build # Build all packages and apps
pnpm typecheck # Type check all workspaces
pnpm lint # Lint and type check
pnpm fix # Auto-fix linting + format
pnpm test # Run tests

# Workspace-specific (use --filter)
pnpm --filter '@dafthunk/api' dev # API dev server (port 3001)
pnpm --filter '@dafthunk/web' dev # Web dev server (port 3000)
pnpm --filter '@dafthunk/api' test:integration # Integration tests

# Database migrations
pnpm --filter '@dafthunk/api' db:migrate # Apply migrations locally
pnpm --filter '@dafthunk/api' db:generate # Generate new migrations
pnpm --filter '@dafthunk/api' db:prod:migrate # Apply to production
```

## Architecture

### Backend: API (`apps/api/`)

**Routes** (`src/routes/`)
- Organized by feature (workflows, executions, objects, etc.)
- Stateless: each request is self-contained
- Auth in `src/auth.ts` (JWT + API Keys)
- Multi-tenant: always scope by `organizationId` from context (`c.get("organizationId")`)
- Validate with Zod + `@hono/zod-validator`

**Database** (`src/db/`)
- D1 (SQLite) + Drizzle ORM
- Schema: `schema/index.ts`
- Queries: `queries.ts`
- Migrations: `migrations/` (generate with `drizzle-kit`)
- Convention: `snake_case` in SQL, `camelCase` in TypeScript

**Workflow Runtime** (`src/runtime/`)
- `runtime.ts` - Cloudflare Workflows for durable execution
- Durable Objects manage state
- `object-store.ts` - Node outputs (R2 + transient storage)
- Executes nodes by graph topology

**Node System** (`src/nodes/`)
- node types in category folders: `text/`, `image/`, `audio/`, `browser/`, `logic/`, `math/`, `javascript/`, `anthropic/`, `openai/`, `gemini/`, `3d/`, `date/`, `document/`, `email/`, `geo/`, `json/`, `net/`, `parameter/`, `rag/`
- Registry: `base-node-registry.ts` and `cloudflare-node-registry.ts`
- All implement common interface from `packages/types`

### Frontend: Web (`apps/web/`)

**Structure**
- Pages: `src/pages/` (one file per route)
- Components: `src/components/` (`ui/` = shadcn/ui, `workflow/` = React Flow editor)
- Routes: `src/routes.tsx` (React Router v7)
- Services: `src/services/` (API clients)

**Patterns**
- Data fetching: SWR (consolidate related calls)
- Styling: Tailwind CSS only (use `cn()` utility)
- State: Avoid `useEffect`, prefer derived state

### Shared: Types (`packages/types/`)
- Single source of truth for data structures
- Backend serializes, frontend deserializes/validates
- Ensures type safety across stack

## Design Principles

When writing or refactoring code:

### Simplify Interfaces
- Export only what's necessary—hide everything else
- Keep public APIs small (fewer exports = less complexity)
- Use barrel exports (`index.ts`) to define module boundaries
- If a function/class can't be described in one sentence, split it

### Manage Complexity
- Push complexity into lower-level modules with simple APIs
- Eliminate unnecessary state, conditionals, and abstractions
- Keep related logic together; separate unrelated concerns
- Depend on interfaces/types, not concrete implementations

### Prioritize Maintainability
- Write the calling code you want first, then implement to match
- After code works, refactor to simplify the interface
- Use comments for *why* (design decisions, trade-offs), not *what* (code explains itself)
- Front-load architectural decisions (module boundaries, data flow); defer details (naming, parameters)

## Code Guidelines

### TypeScript Style
- Strict mode: never use `any` or `unknown`
- Prefer `interface` over `type` for object shapes
- Always use `import type` for type-only imports
- Use early returns to avoid deep nesting

### Naming Conventions
```
Files: kebab-case.tsx
Functions: camelCase()
Hooks: useCamelCase()
Event handlers: handleClick()
Components: PascalCase
```

### React (apps/web)
```tsx
// ✓ Correct
import { Link } from 'react-router' // not react-router-dom
import type { User } from '@dafthunk/types'
export function MyComponent() { ... } // functional component

// Data fetching
const { data } = useSWR(['/users', '/posts'], fetchAll) // consolidate

// Styling
<div className={cn('base-class', isActive && 'active')} />

// Avoid useEffect - prefer derived state or move logic outside React
```

### Hono API (apps/api)
```ts
// Routes by feature
const workflows = new Hono()
workflows.get('/', zValidator('query', schema), (c) => {
const orgId = c.get('organizationId') // always scope by org
// ...
})
app.route('/workflows', workflows)

// Database
const users = sqliteTable('users', {
createdAt: text('created_at'), // snake_case in DB
})
export type User = InferModel<typeof users>
```

### Testing
```ts
// Unit tests: *.test.ts
import { describe, it, expect } from 'vitest'

// Integration tests: *.integration.ts
```
1 change: 1 addition & 0 deletions apps/api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ dist

.dev.vars
.wrangler/
worker-configuration.d.ts

# macOS
.DS_Store
9 changes: 8 additions & 1 deletion apps/api/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import globals from "globals";
import tseslint from "typescript-eslint";

export default defineConfig([
{ ignores: ["dist", ".wrangler/**", "node_modules/**"] },
{
ignores: [
"dist",
".wrangler/**",
"node_modules/**",
"worker-configuration.d.ts",
],
},
{
files: ["**/*.{js,mjs,cjs,ts}"],
plugins: {
Expand Down
3 changes: 1 addition & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.58",
"@cloudflare/workers-types": "^4.20250726.0",
"@eslint/js": "^9.26.0",
"@types/mailparser": "^3.4.6",
"@types/node": "^22.15.3",
Expand All @@ -38,7 +37,7 @@
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
"vitest": "^3.2.4",
"wrangler": "^4.26.1"
"wrangler": "^4.42.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.62.0",
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { JWTTokenPayload } from "@dafthunk/types";

import { WorkflowSession } from "./durable-objects/workflow-session";
import { RuntimeParams } from "./runtime/runtime";

export interface Bindings {
Expand All @@ -9,11 +10,12 @@ export interface Bindings {
RATE_LIMIT_AUTH: RateLimit;
RATE_LIMIT_EXECUTE: RateLimit;
EXECUTE: Workflow<RuntimeParams>;
WORKFLOW_SESSION: DurableObjectNamespace<WorkflowSession>;
RESSOURCES: R2Bucket;
DATASETS: R2Bucket;
DATASETS_AUTORAG: string;
AI: Ai;
BROWSER: Fetcher;
BROWSER?: Fetcher;
COMPUTE: AnalyticsEngineDataset;
WEB_HOST: string;
EMAIL_DOMAIN: string;
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/cron.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ExecutionContext } from "@cloudflare/workers-types";
import { Node, Workflow as WorkflowType } from "@dafthunk/types";
import CronParser from "cron-parser";

Expand Down Expand Up @@ -51,7 +50,6 @@ async function executeWorkflow(
nodes: workflowData.nodes,
edges: workflowData.edges,
},
monitorProgress: false,
deploymentId: deploymentId,
},
});
Expand Down
1 change: 0 additions & 1 deletion apps/api/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { D1Database } from "@cloudflare/workers-types";
import { drizzle } from "drizzle-orm/d1";
import { type DrizzleD1Database } from "drizzle-orm/d1";

Expand Down
36 changes: 36 additions & 0 deletions apps/api/src/db/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,42 @@ export async function getWorkflow(
return workflow?.workflows;
}

/**
* Get a workflow that the user has access to through their organization memberships
*
* @param db Database instance
* @param workflowIdOrHandle Workflow ID or handle
* @param userId User ID to check access for
* @returns The workflow and organization ID if user has access, undefined otherwise
*/
export async function getWorkflowWithUserAccess(
db: ReturnType<typeof createDatabase>,
workflowIdOrHandle: string,
userId: string
): Promise<{ workflow: WorkflowRow; organizationId: string } | undefined> {
const [result] = await db
.select({
workflow: workflows,
organizationId: workflows.organizationId,
})
.from(workflows)
.innerJoin(
memberships,
eq(workflows.organizationId, memberships.organizationId)
)
.where(
and(
eq(memberships.userId, userId),
getWorkflowCondition(workflowIdOrHandle)
)
)
.limit(1);

return result
? { workflow: result.workflow, organizationId: result.organizationId }
: undefined;
}

/**
* Get the latest deployment for a workflow
*
Expand Down
Loading