agentsclimarketplace

Resonate lovable usage prompt typescript

Skill resonatehq/resonate-skills/resonate-lovable-usage-prompt-typescript

Agent skills for building with Resonate — durable execution for long-running, crash-safe workflows.

Install
npx -y skills add resonatehq/resonate-skills --skill resonate-lovable-usage-prompt-typescript

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Specialized guidance for building Resonate applications within Lovable.dev, which is Node.js/React/TypeScript. Use when working with Lovable's AI-assisted development environment to scaffold, iterate, and deploy Resonate workflows in the TypeScript SDK.

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

23.2 KB, as published. Nobody here has run it

Resonate Lovable Usage Prompt — TypeScript

SDK version: This skill reflects @resonatehq/sdk v0.11.2 (current on npm).

Overview

This skill provides specialized guidance for building Resonate durable execution applications within Lovable.dev, an AI-assisted full-stack development environment. Lovable enables rapid prototyping and deployment of React + Node.js applications with built-in hosting.

Core Philosophy: Resonate IS the Database

Critical insight: For workflow state, Resonate is your single source of truth. You don't need a separate database to track workflow status, pending approvals, or execution state.

Traditional approach (unnecessary complexity):
  Create workflow → Store in DB → Poll DB for status → Sync with Resonate

Resonate approach (simple):
  Create workflow → Query Resonate directly → Done

Why this matters:

  • Query promises by ID prefix to list pending items
  • Resolve promises to complete workflows
  • No DB sync logic, no stale state, no race conditions

When you DO need a database:

  • Business data that outlives workflows (user profiles, orders, products)
  • Analytics and reporting
  • Data that needs relational queries

When you DON'T need a database:

  • Workflow status (use Resonate promises)
  • Pending approvals (query by prefix)
  • Execution state (Resonate tracks this)

Lovable Environment Constraints

What Lovable Provides

  • React frontend (Vite + TypeScript)
  • Node.js backend (Express + TypeScript)
  • PostgreSQL database (Supabase)
  • Built-in deployment pipeline
  • AI-assisted code generation
  • Git integration

Lovable Limitations for Resonate

Lovable does NOT provide:

  • Long-running backend processes
  • WebSocket servers
  • Background workers
  • Cron jobs or schedulers

Implication: You cannot run a persistent Resonate worker inside Lovable's backend.

Recommended Architecture for Lovable + Resonate

Architecture A: External Resonate Server (Recommended)

Lovable backend acts as HTTP client to external Resonate:

Lovable Frontend (React)
        ↓
Lovable Backend (Express) [HTTP Client Only]
        ↓
External Resonate Server (Cloud Run, Fly.io, Railway)
        ↓
Resonate Workers (separate deployment)

Lovable backend code:

// server/routes/workflows.ts
import { Resonate } from "@resonatehq/sdk";
import express from "express";

const router = express.Router();

// Connect to external Resonate server
const resonate = new Resonate({
  url: process.env.RESONATE_URL,  // e.g., https://resonate.example.com
  token: process.env.RESONATE_TOKEN,  // JWT token for auth (if server requires it)
  group: "lovable-client"
});

// Start a workflow (non-blocking)
router.post("/api/workflows/start", async (req, res) => {
  const { workflowType, input } = req.body;
  const workflowId = `workflow-${Date.now()}`;

  await resonate.beginRpc(
    workflowId,
    workflowType,
    input,
    resonate.options({
      target: "poll://any@workers"  // Routes to external workers
    })
  );

  res.json({
    workflowId,
    status: "started",
    pollUrl: `/api/workflows/${workflowId}/status`
  });
});

// Check workflow status
router.get("/api/workflows/:id/status", async (req, res) => {
  try {
    const handle = await resonate.get(req.params.id);
    const result = await handle.result();

    res.json({
      workflowId: req.params.id,
      status: "completed",
      result
    });
  } catch (error) {
    res.json({
      workflowId: req.params.id,
      status: "pending"
    });
  }
});

export default router;

External workers (deployed separately):

// workers/index.ts (deployed to Cloud Run/Fly.io)
import { Resonate, type Context } from "@resonatehq/sdk";

const resonate = new Resonate({
  url: process.env.RESONATE_URL,
  token: process.env.RESONATE_TOKEN,  // JWT token for auth
  group: "workers"
});

function* processOrder(ctx: Context, input: any) {
  // Durable workflow logic
  const validated = yield* ctx.run(validateOrder, input);
  const payment = yield* ctx.run(processPayment, validated);
  const shipment = yield* ctx.run(createShipment, payment);

  return { status: "success", shipment };
}

resonate.register("processOrder", processOrder);

// Keep worker alive
process.on("SIGTERM", () => process.exit(0));

Architecture B: Supabase Edge Functions (Alternative)

If using Lovable with Supabase Edge Functions, see the resonate-supabase-deployments-typescript skill for complete Deno-specific patterns including:

  • @resonatehq/supabase shim usage
  • start/ and probe/ endpoint patterns
  • Deno.serve() request handling

Limitation: Supabase Edge Functions have 30-second timeout, so workflows must complete quickly or use async patterns.

HTTP API vs SDK: When to Use Which

This is the most important decision when building with Lovable + Resonate.

Use the SDK (Programming Model) When:

ActionSDK MethodExample
Start a workflowresonate.run(), resonate.rpc(), beginRun(), beginRpc()Starting an order processing workflow
Execute durable codeGenerator functions with ctx.run(), ctx.sleep()The workflow logic itself
Register workflow handlersresonate.register()Setting up workers

Key insight: The SDK is for executing workflows. You need a process that can run the workflow code.

Use the HTTP API Directly When:

ActionHTTP MethodExample
List promises by prefixGET /promises?id=prefix-*Showing all pending approvals in UI
Get promise stateGET /promises/{id}Checking if a workflow completed
Resolve a HITL promisePATCH /promises/{id}User clicking "Approve" button
Create a standalone promisePOST /promisesExternal system creating a promise to be resolved later

Key insight: The HTTP API is for managing promise state without running workflow code.

Decision Flowchart

Do you need to RUN workflow code (generators, ctx.run, ctx.sleep)?
  │
  ├─ YES → Use SDK: resonate.run(), resonate.rpc(), etc.
  │         (Requires a worker process that can execute the code)
  │
  └─ NO → Are you querying or resolving existing promises?
           │
           ├─ YES → Use HTTP API: GET/PATCH /promises
           │         (Can be done from any HTTP client)
           │
           └─ NO → You probably need the SDK

Lovable-Specific Guidance

Since Lovable cannot run persistent workers, your Lovable backend should:

  1. Use SDK to START workflows on external workers: resonate.beginRpc() with target: "poll://any@workers"
  2. Use HTTP API to QUERY workflow state: GET /promises?id=...
  3. Use HTTP API or SDK to RESOLVE promises: PATCH /promises/{id} or resonate.promises.resolve(id, { data })

The actual workflow EXECUTION happens on your external workers (Cloud Run, Fly.io, etc.), not in Lovable.

Lovable-Specific Patterns

Pattern 1: Async Workflow with Polling

Frontend (React):

// src/components/WorkflowRunner.tsx
import { useState } from "react";
import { useToast } from "@/components/ui/use-toast";

export function WorkflowRunner() {
  const [workflowId, setWorkflowId] = useState<string | null>(null);
  const [status, setStatus] = useState<"idle" | "running" | "completed">("idle");
  const { toast } = useToast();

  const startWorkflow = async () => {
    setStatus("running");

    const response = await fetch("/api/workflows/start", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        workflowType: "processOrder",
        input: { orderId: "123", items: [...] }
      })
    });

    const { workflowId } = await response.json();
    setWorkflowId(workflowId);

    // Poll for completion
    const pollInterval = setInterval(async () => {
      const statusRes = await fetch(`/api/workflows/${workflowId}/status`);
      const data = await statusRes.json();

      if (data.status === "completed") {
        clearInterval(pollInterval);
        setStatus("completed");
        toast({ title: "Workflow completed!", description: JSON.stringify(data.result) });
      }
    }, 2000);
  };

  return (
    <div>
      <button onClick={startWorkflow} disabled={status === "running"}>
        Start Workflow
      </button>
      {status === "running" && <p>Workflow running... (ID: {workflowId})</p>}
      {status === "completed" && <p>Workflow completed!</p>}
    </div>
  );
}

Pattern 2: Store Workflow State in Supabase

Track workflow progress in Lovable's built-in PostgreSQL:

// server/routes/workflows.ts
router.post("/api/workflows/start", async (req, res) => {
  const { workflowType, input } = req.body;
  const workflowId = `workflow-${Date.now()}`;

  // Store in database
  await supabase.from("workflows").insert({
    id: workflowId,
    type: workflowType,
    input,
    status: "pending",
    created_at: new Date().toISOString()
  });

  // Start Resonate workflow
  await resonate.beginRpc(workflowId, workflowType, input, resonate.options({
    target: "poll://any@workers"
  }));

  res.json({ workflowId });
});

// Webhook for completion (called by external worker)
router.post("/api/workflows/:id/complete", async (req, res) => {
  const { id } = req.params;
  const { result } = req.body;

  await supabase.from("workflows").update({
    status: "completed",
    result,
    completed_at: new Date().toISOString()
  }).eq("id", id);

  res.json({ success: true });
});

Pattern 3: Human-in-the-Loop with Lovable UI

Workflow creates approval request, Lovable UI resolves it:

// External worker
function* approvalWorkflow(ctx: Context, orderId: string) {
  const promise = yield* ctx.promise({
    id: `approval-${orderId}`,
    timeout: 24 * 60 * 60 * 1000
  });

  // Notify Lovable backend
  yield* ctx.run(async () => {
    await fetch(`${lovableBackendUrl}/api/approvals/create`, {
      method: "POST",
      body: JSON.stringify({
        orderId,
        promiseId: promise.id
      })
    });
  });

  const decision = yield* promise;
  return decision;
}

// Lovable backend
router.post("/api/approvals/resolve/:promiseId", async (req, res) => {
  const { promiseId } = req.params;
  const { approved } = req.body;

  const data = Buffer.from(JSON.stringify({ approved })).toString('base64');

  await resonate.promises.resolve(promiseId, { data });

  res.json({ success: true });
});

// Lovable frontend
function ApprovalButton({ promiseId }: { promiseId: string }) {
  const handleApprove = async () => {
    await fetch(`/api/approvals/resolve/${promiseId}`, {
      method: "POST",
      body: JSON.stringify({ approved: true })
    });
  };

  return <button onClick={handleApprove}>Approve</button>;
}

Deployment Strategy

Step 1: Deploy Resonate Server

Options:

  • Google Cloud Run (recommended for Lovable users)
  • Fly.io
  • Railway
  • Render

Example (Cloud Run):

# Deploy Resonate server
docker pull resonatehq/resonate:latest
gcloud run deploy resonate-server \
  --image resonatehq/resonate:latest \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated

Step 2: Deploy Resonate Workers

Same platforms, separate service:

# Build and deploy worker
docker build -t workers .
gcloud run deploy resonate-workers \
  --image gcr.io/PROJECT/workers \
  --platform managed \
  --set-env-vars RESONATE_URL=https://resonate-server-xxx.run.app

Step 3: Configure Lovable Backend

Set environment variable in Lovable:

RESONATE_URL=https://resonate-server-xxx.run.app

Resonate HTTP API Reference (Direct Calls)

When using Lovable with an external Resonate server, you'll make direct HTTP calls. Here's the complete API reference.

Server Configuration

const RESONATE_URL = "https://resonate-server.example.com";  // Your Resonate server
const RESONATE_TOKEN = process.env.RESONATE_TOKEN; // Optional auth token

const headers = {
  "Content-Type": "application/json",
  ...(RESONATE_TOKEN && { "Authorization": `Bearer ${RESONATE_TOKEN}` })
};

Promise States

Resonate promises have these states:

StateDescription
PENDINGPromise created, waiting for resolution
RESOLVEDPromise completed successfully
REJECTEDPromise failed/rejected
REJECTED_TIMEDOUTPromise exceeded timeout
REJECTED_CANCELEDPromise was canceled

Create a Promise

Endpoint: POST /promises

Request:

{
  "id": "approval-request-abc123",
  "timeout": 86400000,
  "param": {
    "headers": {},
    "data": "eyJvcmRlcklkIjoiMTIzIn0="
  },
  "tags": {
    "type": "approval",
    "created_by": "[email protected]"
  }
}

Notes:

  • id: Use a prefix convention like approval-request- for easy querying
  • timeout: Epoch milliseconds from now (NOT ISO string). Example: 86400000 = 24 hours
  • param.data: Base64-encoded JSON for initial data (optional)
  • tags: Key-value metadata for filtering (optional)

Response:

{
  "id": "approval-request-abc123",
  "state": "PENDING",
  "timeout": 1706486400000,
  "param": { "headers": {}, "data": "eyJvcmRlcklkIjoiMTIzIn0=" },
  "value": { "headers": {}, "data": null },
  "tags": { "type": "approval" },
  "createdOn": 1706400000000,
  "completedOn": null
}

TypeScript Example:

async function createApprovalRequest(requestId: string, data: any) {
  const response = await fetch(`${RESONATE_URL}/promises`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      id: `approval-request-${requestId}`,
      timeout: Date.now() + (24 * 60 * 60 * 1000), // 24 hours from now
      param: {
        headers: {},
        data: btoa(JSON.stringify(data)) // Base64 encode
      }
    })
  });
  return response.json();
}

Resolve a Promise

Endpoint: PATCH /promises/{id}

Request (approve):

{
  "state": "RESOLVED",
  "value": {
    "data": "eyJhcHByb3ZlZCI6dHJ1ZX0="
  }
}

Request (reject):

{
  "state": "REJECTED",
  "value": {
    "data": "eyJhcHByb3ZlZCI6ZmFsc2UsInJlYXNvbiI6IkJ1ZGdldCBleGNlZWRlZCJ9"
  }
}

Notes:

  • value.data: MUST be Base64-encoded JSON
  • Set state to RESOLVED for approval, REJECTED for rejection

TypeScript Example:

async function resolveApproval(promiseId: string, approved: boolean, reason?: string) {
  const decision = { approved, reason, resolvedAt: Date.now() };
  const encodedData = btoa(JSON.stringify(decision)); // Base64 encode

  const response = await fetch(`${RESONATE_URL}/promises/${promiseId}`, {
    method: "PATCH",
    headers,
    body: JSON.stringify({
      state: approved ? "RESOLVED" : "REJECTED",
      value: { data: encodedData }
    })
  });
  return response.json();
}

Get a Single Promise

Endpoint: GET /promises/{id}

Response:

{
  "id": "approval-request-abc123",
  "state": "PENDING",
  "timeout": 1706486400000,
  "param": { "headers": {}, "data": "eyJvcmRlcklkIjoiMTIzIn0=" },
  "value": { "headers": {}, "data": null },
  "tags": { "type": "approval" },
  "createdOn": 1706400000000,
  "completedOn": null
}

TypeScript Example:

async function getApprovalRequest(promiseId: string) {
  const response = await fetch(`${RESONATE_URL}/promises/${promiseId}`, {
    method: "GET",
    headers
  });
  const promise = await response.json();

  // Decode the data if present
  if (promise.param?.data) {
    promise.decodedParam = JSON.parse(atob(promise.param.data));
  }
  if (promise.value?.data) {
    promise.decodedValue = JSON.parse(atob(promise.value.data));
  }

  return promise;
}

List Promises by Prefix (Query)

Endpoint: GET /promises?id={prefix}*&limit={n}&state={state}

Query Parameters:

  • id: Prefix pattern with wildcard, e.g., approval-request-*
  • limit: Max results (default 10, max usually 100)
  • state: Filter by state: pending, resolved, rejected

Example Queries:

GET /promises?id=approval-request-*&limit=50
GET /promises?id=approval-request-*&state=pending&limit=20
GET /promises?id=countdown-*&state=pending

Response:

{
  "promises": [
    {
      "id": "approval-request-abc123",
      "state": "PENDING",
      "timeout": 1706486400000,
      ...
    },
    {
      "id": "approval-request-def456",
      "state": "RESOLVED",
      ...
    }
  ],
  "cursor": "next-page-token"
}

TypeScript Example:

async function listPendingApprovals() {
  const response = await fetch(
    `${RESONATE_URL}/promises?id=approval-request-*&state=pending&limit=50`,
    { method: "GET", headers }
  );
  const { promises } = await response.json();

  // Decode all param data
  return promises.map(p => ({
    ...p,
    decodedParam: p.param?.data ? JSON.parse(atob(p.param.data)) : null
  }));
}

Complete Lovable Pattern: No Database Needed

// Create approval - Resonate is the source of truth
app.post("/api/approvals", async (req, res) => {
  const requestId = crypto.randomUUID();
  const promise = await createApprovalRequest(requestId, req.body);
  res.json({ requestId, promiseId: promise.id });
});

// List pending approvals - Query Resonate directly
app.get("/api/approvals", async (req, res) => {
  const approvals = await listPendingApprovals();
  res.json(approvals);
});

// Get single approval
app.get("/api/approvals/:id", async (req, res) => {
  const approval = await getApprovalRequest(`approval-request-${req.params.id}`);
  res.json(approval);
});

// Resolve approval
app.post("/api/approvals/:id/resolve", async (req, res) => {
  const { approved, reason } = req.body;
  await resolveApproval(`approval-request-${req.params.id}`, approved, reason);
  res.json({ success: true });
});

Timeout Calculation

IMPORTANT: Timeouts are absolute epoch milliseconds, not durations.

// ❌ WRONG - This is a duration, not a timestamp
const timeout = 24 * 60 * 60 * 1000;

// ✅ CORRECT - Absolute timestamp (now + duration)
const timeout = Date.now() + (24 * 60 * 60 * 1000);

// Common patterns:
const in1Hour = Date.now() + (60 * 60 * 1000);
const in24Hours = Date.now() + (24 * 60 * 60 * 1000);
const in7Days = Date.now() + (7 * 24 * 60 * 60 * 1000);

Lovable AI Prompt Templates

Prompt 1: Scaffold Resonate Client

Create a Resonate client module in server/lib/resonate.ts that:
1. Connects to process.env.RESONATE_URL
2. Uses group "lovable-client"
3. Exports a configured Resonate instance
4. Includes TypeScript types for workflow inputs/outputs

Prompt 2: Create Workflow API Routes

Create Express routes in server/routes/workflows.ts that:
1. POST /api/workflows/start - starts a workflow via resonate.beginRpc
2. GET /api/workflows/:id/status - checks workflow status
3. POST /api/workflows/:id/cancel - cancels a workflow
4. Include error handling and TypeScript types

Prompt 3: Build React UI for Workflows

Create a React component WorkflowDashboard that:
1. Lists all workflows from Supabase "workflows" table
2. Shows workflow status (pending/running/completed/failed)
3. Allows starting new workflows
4. Polls for status updates every 3 seconds
5. Uses shadcn/ui components (button, card, badge)

Example: Complete Lovable + Resonate App

Use case: Order processing with approval

1. External Worker (Cloud Run)

// worker/index.ts
import { Resonate, type Context } from "@resonatehq/sdk";

const resonate = new Resonate({
  url: process.env.RESONATE_URL,
  token: process.env.RESONATE_TOKEN,  // JWT token for auth
  group: "workers"
});

function* processOrder(ctx: Context, order: any) {
  // Validate
  const valid = yield* ctx.run(validateOrder, order);

  // Create approval promise
  const approval = yield* ctx.promise({
    id: `approval-${order.id}`,
    timeout: 24 * 60 * 60 * 1000
  });

  // Notify Lovable
  yield* ctx.run(notifyLovable, order.id, approval.id);

  // Wait for human approval
  const decision = yield* approval;

  if (!decision.approved) {
    return { status: "rejected" };
  }

  // Process payment
  const payment = yield* ctx.run(chargePayment, order);

  return { status: "approved", payment };
}

resonate.register("processOrder", processOrder);

2. Lovable Backend

// server/routes/orders.ts
import { resonate, supabase } from "../lib";

router.post("/api/orders", async (req, res) => {
  const order = req.body;
  const orderId = `order-${Date.now()}`;

  // Store in database
  await supabase.from("orders").insert({
    id: orderId,
    data: order,
    status: "pending"
  });

  // Start workflow
  await resonate.beginRpc(
    orderId,
    "processOrder",
    order,
    resonate.options({ target: "poll://any@workers" })
  );

  res.json({ orderId });
});

router.post("/api/approvals/:promiseId", async (req, res) => {
  const { promiseId } = req.params;
  const { approved } = req.body;

  const data = Buffer.from(JSON.stringify({ approved })).toString('base64');
  await resonate.promises.resolve(promiseId, { data });

  res.json({ success: true });
});

3. Lovable Frontend

// src/pages/Orders.tsx
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";

export function Orders() {
  const [orders, setOrders] = useState([]);

  const createOrder = async () => {
    const response = await fetch("/api/orders", {
      method: "POST",
      body: JSON.stringify({
        items: ["item1", "item2"],
        total: 100
      })
    });

    const { orderId } = await response.json();
    // Refresh list
  };

  return (
    <div>
      <Button onClick={createOrder}>Create Order</Button>
      <OrderList orders={orders} />
    </div>
  );
}

Common Lovable + Resonate Patterns

  1. Lovable as orchestration UI - Resonate for durable execution
  2. Supabase for state - Resonate for workflows
  3. Polling for updates - webhooks for completions
  4. External workers - Lovable as control plane

Limitations to Explain

When building with Lovable + Resonate, tell users:

  1. Lovable backend is stateless HTTP - cannot run persistent workers
  2. External Resonate deployment required
  3. Polling needed for workflow status (no WebSockets in Lovable)
  4. Database updates via Supabase for real-time UI

Quick Start Checklist

  • Deploy Resonate server (Cloud Run recommended)
  • Deploy Resonate workers (same platform)
  • Add RESONATE_URL to Lovable environment variables
  • Create Resonate client module in Lovable backend
  • Create workflow API routes in Lovable backend
  • Build React UI components for workflow management
  • Add Supabase table for workflow state tracking
  • Test end-to-end workflow execution

Summary

Lovable + Resonate enables rapid development of durable execution applications by:

  • Using Lovable for UI and HTTP API layer
  • Using Resonate for durable workflows and coordination
  • Deploying workers separately on Cloud Run/Fly.io
  • Connecting via HTTP APIs and promise resolution

Key principle: Lovable handles the user interface, Resonate handles the durable execution.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.