AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Resonate Lovable Usage Prompt Typescript

skill-resonatehq-resonate-skills-resonate-lovable-usage-prompt-typescript · by resonatehq

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.

— No reviews yet
0 installs
45 views
0.0% view→install

Install

$ agentstack add skill-resonatehq-resonate-skills-resonate-lovable-usage-prompt-typescript

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ● Network access Used
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-resonatehq-resonate-skills-resonate-lovable-usage-prompt-typescript)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Resonate Lovable Usage Prompt Typescript? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Resonate Lovable Usage Prompt — TypeScript

> SDK version: This skill reflects @resonatehq/sdk v0.10.0 (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:

| Action | SDK Method | Example | |--------|------------|---------| | Start a workflow | resonate.run(), resonate.rpc(), beginRun(), beginRpc() | Starting an order processing workflow | | Execute durable code | Generator functions with ctx.run(), ctx.sleep() | The workflow logic itself | | Register workflow handlers | resonate.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:

| Action | HTTP Method | Example | |--------|-------------|---------| | List promises by prefix | GET /promises?id=prefix-* | Showing all pending approvals in UI | | Get promise state | GET /promises/{id} | Checking if a workflow completed | | Resolve a HITL promise | PATCH /promises/{id} | User clicking "Approve" button | | Create a standalone promise | POST /promises | External 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 SETTLE promises: PATCH /promises/{id} or resonate.promises.settle(id, "resolved", { 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(null);
  const [status, setStatus] = useState("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 (
    
      
        Start Workflow
      
      {status === "running" && Workflow running... (ID: {workflowId})}
      {status === "completed" && Workflow completed!}
    
  );
}

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.settle(promiseId, "resolved", { 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 Approve;
}

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:

| State | Description | |-------|-------------| | PENDING | Promise created, waiting for resolution | | RESOLVED | Promise completed successfully | | REJECTED | Promise failed/rejected | | REJECTED_TIMEDOUT | Promise exceeded timeout | | REJECTED_CANCELED | Promise was canceled |

Create a Promise

Endpoint: POST /promises

Request:

{
  "id": "approval-request-abc123",
  "timeout": 86400000,
  "param": {
    "headers": {},
    "data": "eyJvcmRlcklkIjoiMTIzIn0="
  },
  "tags": {
    "type": "approval",
    "created_by": "user@example.com"
  }
}

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

*

…

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.