@runworkai/framework
v0.4.5
Published
The TypeScript framework for building full-stack apps on the Runwork platform — entity storage, AI agents, workflows, integrations, and more.
Downloads
416
Maintainers
Readme
@runworkai/framework
The TypeScript framework for building apps on Runwork — the AI workspace where teams talk to AI, get custom tools, and work together.
Every Runwork app comes with built-in data storage, AI agents, durable workflows, scheduled jobs, file storage, third-party integrations, real-time channels, and cross-app workspaces — all from a single codebase, deployed and managed by the platform.
Quick Start
npm install @runworkai/framework// worker/index.ts
import { createApp } from '@runworkai/framework'
import { entities } from './entities'
import { routes } from './routes'
createApp({ entities, routes })The Runwork Platform
Runwork is an AI workspace that brings AI agents, custom apps, and human teams into one place. Teams talk to the workspace to get quick answers, automate recurring work, or spin up full custom tools — without switching between dozens of SaaS products.
This framework is the developer interface to the platform. You write TypeScript, and the platform handles deployment, infrastructure, AI, and integrations:
- Instant deployments — Push code and get a live URL. Preview environments for every branch, production at the edge.
- Built-in AI agents — Users interact with conversational agents that read data, take actions, and coordinate across apps. No API keys to manage.
- Zero to app in seconds — Describe what you need and Runwork generates it. Developers customize with code using this framework.
- Connected apps — Apps in a workspace share entities, call each other's APIs, communicate through channels, and present a unified experience.
- 3,200+ integrations — GitHub, Slack, HubSpot, Google, and thousands more via OAuth. The platform manages tokens and provides a proxy API.
- No infrastructure to manage — Storage, compute, file hosting, cron scheduling, and workflow orchestration are all handled by the platform.
- Local dev with any AI tool — Use the Runwork CLI (
runwork dev), OpenAI Codex, Claude Code, Cursor, or any editor and AI code agent.
Sub-path Exports
The framework uses sub-path exports to keep imports clean and tree-shakeable:
| Import | Description |
|--------|-------------|
| @runworkai/framework | Core utilities: Env, ok, bad, notFound, isStr, createApp |
| @runworkai/framework/entities | Data storage: Entity base classes, CRUD operations, indexing |
| @runworkai/framework/agents | AI agent definitions and conversational runtime |
| @runworkai/framework/ai | Backend AI helpers: generateText, generateObject, streamText, tool |
| @runworkai/framework/workflows | Multi-step durable workflow definitions and triggers |
| @runworkai/framework/scheduler | Scheduled background job definitions |
| @runworkai/framework/endpoints | Public API endpoint definitions with schema validation |
| @runworkai/framework/routes | Internal route types |
| @runworkai/framework/workspace | Cross-app workspace operations and entity access |
| @runworkai/framework/storage | File/blob storage client |
| @runworkai/framework/integrations | Third-party integration client (OAuth proxy) |
| @runworkai/framework/channels | Workspace channel messaging |
| @runworkai/framework/events | Event emission system |
| @runworkai/framework/components | Cross-app reusable UI component definitions |
| @runworkai/framework/vite | Vite plugin for development and builds |
| @runworkai/framework/types | Shared type definitions |
Features
Data Storage (Entities)
Persistent data storage with search, sort, filter, paginate, and bulk operations. Define Entity classes for your data models, and the platform handles persistence. Data is accessible across apps in a workspace.
// worker/entities.ts
import { Entity } from '@runworkai/framework/entities';
interface TodoState {
id: string;
title: string;
completed: boolean;
userId: string;
}
export class TodoEntity extends Entity<TodoState> {
static readonly entityName = "Todo";
static readonly initialState: TodoState = {
id: "", title: "", completed: false, userId: ""
};
// Custom methods for domain logic
async toggle() {
await this.mutate(state => ({ ...state, completed: !state.completed }));
return this.getState();
}
}API Routes
Define routes using Hono or the declarative EndpointDefinition format. Routes using EndpointDefinition are automatically registered with the workspace, making them discoverable by other apps and AI agents.
import { ok, bad } from '@runworkai/framework'
import type { EndpointDefinition } from '@runworkai/framework/endpoints'
import { z } from 'zod'
export const APP_ROUTES = [
{
path: '/api/todos',
method: 'GET',
auth: 'none',
schema: {
query: z.object({ limit: z.coerce.number().optional().default(50) }),
},
handler: async (ctx) => {
const page = await TodoEntity.list({ env: ctx.env }, { limit: ctx.query.limit })
return page
},
meta: { description: 'List todos', tags: ['todos'] },
},
]AI Agents
Conversational AI agents with tool use, memory, and integration access. Agents can read/write entities, call connected integrations, and maintain conversation history. The platform handles model selection, API keys, and billing.
// worker/agents.ts
import type { AgentDefinition } from '@runworkai/framework/agents';
export const APP_AGENTS: AgentDefinition[] = [
{
name: 'support-assistant',
description: 'Helps users with common questions and issues',
type: 'conversational',
systemPrompt: `You are a helpful support assistant for our application.
Your capabilities:
- Answer questions about the product
- Help users troubleshoot issues
- Create GitHub issues for bug reports
- Send Slack notifications when needed
Be friendly, professional, and concise.`,
integrations: ['github', 'slack'],
entities: ['ticket', 'user'],
memoryStrategy: 'user',
},
];Backend AI Processing
Use AI for backend tasks like classification, extraction, and summarization. Works in routes, workflows, and scheduled jobs. No external AI SDK needed — the platform proxies all LLM calls.
import { z } from 'zod';
import { generateObject } from '@runworkai/framework/ai';
app.post('/api/receipts/parse', async (c) => {
const { receiptText } = await c.req.json();
const result = await generateObject(c.env, {
prompt: `Extract receipt data from: \`,
schema: z.object({
vendor: z.string(),
date: z.string(),
total: z.number(),
items: z.array(z.object({
name: z.string(),
price: z.number(),
})),
}),
});
await ReceiptEntity.create({ env: c.env }, result.object);
return ok(c, result.object);
});Workflows
Durable, multi-step processes that survive failures. Workflows can wait for external events, retry with backoff, sleep for durations, and coordinate across services.
// worker/workflows.ts
import type { WorkflowDefinition } from '@runworkai/framework/workflows';
import { OrderEntity } from "./entities";
export const APP_WORKFLOWS: WorkflowDefinition[] = [
{
name: "order-fulfillment",
description: "Process order through payment and shipping",
handler: async (ctx, step) => {
const entityCtx = { env: ctx.env };
const { orderId } = ctx.params as { orderId: string };
// Step 1: Validate order
const order = await step.do("validate-order", async () => {
return await OrderEntity.get(entityCtx, orderId);
});
// Step 2: Process payment (with retry)
await step.do("process-payment", {
retries: { limit: 3, delay: "10s" },
}, async () => {
// Payment logic here
});
// Step 3: Wait for external confirmation
await step.waitForEvent("shipping-confirmed", {
timeout: "24h",
});
return { success: true, orderId };
},
},
];Scheduled Jobs
Run background tasks on a cron schedule. Jobs are managed by the platform with execution logging and monitoring in the workspace dashboard.
// worker/schedules.ts
import type { ScheduledJob } from '@runworkai/framework/scheduler';
import { TodoEntity } from "./entities";
export const APP_SCHEDULED_JOBS: ScheduledJob[] = [
{
name: "cleanup-completed-todos",
schedule: "0 0 * * *", // Daily at midnight UTC
description: "Remove completed todos older than 30 days",
handler: async (ctx) => {
const entityCtx = { env: ctx.env };
const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
const todos = await TodoEntity.list(entityCtx, { limit: 1000 });
const idsToDelete = todos.items
.filter(todo => todo.completed && todo.completedAt < thirtyDaysAgo)
.map(todo => todo.id);
const deleted = await TodoEntity.deleteMany(entityCtx, idsToDelete);
return { deleted, processed: todos.items.length };
},
},
];File Storage
Upload and manage files. The platform provisions object storage automatically when enabled.
// worker/routes.ts
import { createFileStorageClient } from '@runworkai/framework/storage';
import { ok, notFound } from '@runworkai/framework';
export const APP_ROUTES = [
(app) => {
app.post('/api/documents', async (c) => {
const storage = createFileStorageClient(c.env);
const entityCtx = { env: c.env };
// Upload with custom key and metadata
const file = await c.req.blob();
const metadata = await storage.upload(file, {
key: `documents/\.pdf`,
name: 'report.pdf',
contentType: 'application/pdf',
customMetadata: { category: 'reports', userId: 'user123' }
});
// Track in your own entity
await DocumentEntity.create(entityCtx, {
id: crypto.randomUUID(),
fileKey: metadata.key,
fileName: metadata.name,
uploadedBy: 'user123',
createdAt: Date.now()
});
return ok(c, metadata);
});
app.get('/api/documents/:id/download', async (c) => {
const storage = createFileStorageClient(c.env);
const ctx = { env: c.env };
const doc = await DocumentEntity.get(ctx, c.req.param('id'));
if (!doc) return notFound(c, 'Document not found');
const object = await storage.download(doc.fileKey);
if (!object) return notFound(c, 'File not found');
return new Response(object.body, {
headers: {
'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
'Content-Disposition': `attachment; filename="\"`
}
});
});
},
];Third-Party Integrations
Connect to 3,200+ external services via OAuth. The platform manages OAuth flows and token refresh — your code just calls the proxy.
import { useIntegration } from '@runworkai/framework/integrations'
const github = useIntegration(env, 'github')
const repos = await github.proxy('/user/repos', { method: 'GET' })Cross-App Workspaces & Channels
Apps in a workspace share entities, call each other's APIs, post to channels, and trigger each other's workflows.
// worker/routes.ts
import { postToChannel } from '@runworkai/framework/channels';
import { ok } from '@runworkai/framework';
export const APP_ROUTES = [
(app) => {
// Post alert when stock drops below threshold
app.post('/api/inventory/check-stock', async (c) => {
const { productName, quantity, threshold } = await c.req.json();
if (quantity < threshold) {
postToChannel(c.executionCtx, c.env, {
channel: '#inventory-alerts',
content: `Low stock alert: **\** is at \ units (threshold: \).`,
metadata: { productName, quantity, threshold }
});
}
return ok(c, { checked: true });
});
}
];Public Endpoints
Expose authenticated APIs for external consumers with API key authentication. Accessible at https://{workspace}.runwork.ai/{app}/endpoints/{path}.
// worker/endpoints.ts
import { z } from 'zod';
import type { EndpointDefinition } from '@runworkai/framework/endpoints';
import { EndpointError } from '@runworkai/framework/endpoints';
import { TodoEntity } from './entities';
export const APP_PUBLIC_ENDPOINTS: EndpointDefinition[] = [
{
path: '/v1/todos',
method: 'GET',
auth: 'apiKey', // Requires API key
schema: {
query: z.object({
limit: z.coerce.number().optional().default(50),
}),
response: z.object({
items: z.array(z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
})),
}),
},
handler: async (ctx) => {
const entityCtx = { env: ctx.env };
const { items } = await TodoEntity.list(entityCtx, { limit: ctx.query.limit });
return { items };
},
meta: {
description: 'List all todos',
tags: ['todos'],
},
},
{
path: '/v1/todos',
method: 'POST',
auth: 'apiKey',
schema: {
body: z.object({ title: z.string().min(1) }),
},
handler: async (ctx) => {
const entityCtx = { env: ctx.env };
const todo = await TodoEntity.create(entityCtx, {
title: ctx.body!.title,
completed: false,
});
return todo;
},
meta: { description: 'Create a new todo', tags: ['todos'] },
},
];Reusable Components
Define UI components and backend APIs that other apps in the workspace can embed or call.
export const APP_COMPONENTS: ComponentDefinition[] = [
{
name: "user-card",
title: "User Card",
frontend: true,
backend: true,
metadata: {
tag: "user-card",
description: "Displays user profile information",
},
},
];Local Development
Develop Runwork apps locally using the CLI or any AI coding tool:
# Using the Runwork CLI
runwork dev # Start preview, sync changes
runwork deploy # Deploy to production
# Or use any editor / AI tool
# Framework types are available via node_modules after bun installBuilt With
- Hono — HTTP routing
- Vite — Development server and builds
- React — Frontend UI
- Zod — Schema validation
- Vercel AI SDK — AI/LLM abstraction layer (
generateText,generateObject,streamText) - Cloudflare Agents SDK — Stateful conversational agents
Requirements
Links
License
MIT
