lunora-setup-hyperdrive
Apps & AutomationConnects an existing Postgres/MySQL database to a Lunora app from an action via Cloudflare Hyperdrive. Use for `@lunora/hyperdrive`, `ctx.sql`, `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`, the `HYPERDRIVE` binding, `wrangler hyperdrive create`, and the action-only / non-reactive guardrails (live queries don't track external writes).
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/anolilab/lunora/blob/HEAD/.claude/skills/lunora-setup-hyperdrive/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/lunora-setup-hyperdrive, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
Lunora Setup Hyperdrive
Wire an existing Postgres/MySQL database into a Lunora app using
@lunora/hyperdrive, which surfaces a Cloudflare
Hyperdrive binding's connection
string and a driver-agnostic ctx.sql client — usable only inside an
action.
Integrate, don't replace. Hyperdrive talks to a database Lunora has no visibility into. Queries through
ctx.sqlare non-deterministic (forbidden inquery/mutation, enforced by thehyperdrive_outside_actionadvisor lint) and external writes are invisible to Lunora live queries — subscriptions will NOT re-run when external rows change. Use it to read/write a legacy DB from an action; if you want that data reactive, write a projection into adefineSchemaDO/D1 table.
When to Use
- Reading/writing an existing Postgres or MySQL database from a Lunora action.
- Incrementally migrating off a legacy DB while standing up Lunora alongside it.
- Backfilling/syncing external rows into reactive Lunora tables (projection).
When Not to Use
- You want the external data to be reactive out of the box — it can't be;
Hyperdrive writes don't hit the change-feed. Project into a
defineSchematable instead. - You're reaching for
ctx.sqlinside aqueryormutation— forbidden by design (the advisor lint flags it). Move the access into anaction. - The app has no Lunora backend yet — use
lunora-quickstartfirst. - You'd be replacing
defineSchemaentirely — then you lose realtime, OCC, optimistic updates, the offline queue, and the advisors. Use Lunora's own data layer for app state.
Workflow
- Install
@lunora/hyperdriveand one SQL driver. - Create the Hyperdrive config (
wrangler hyperdrive create) and add theHYPERDRIVEbinding towrangler.jsonc. - Regenerate types with
lunora codegensoctx.sqlappears onActionCtx. - Read/write the external DB from an action via
ctx.sql. - (Optional) Project external rows into a
defineSchematable to make them reactive.
Step 1: Install the package + a driver
pnpm add @lunora/hyperdrive
# choose ONE driver (optional peer deps — none is bundled):
pnpm add postgres # postgres.js → fromPostgresJs ($1, $2 placeholders)
# or
pnpm add pg # node-postgres → fromNodePg ($1, $2 placeholders)
# or
pnpm add mysql2 # mysql2 → fromMysql2 (? placeholders)
Step 2: Create the binding
wrangler hyperdrive create my-db --connection-string="postgres://user:pass@host:5432/db" # gitleaks:allow -- placeholder, not a real secret
This prints an id. Add the binding to wrangler.jsonc, using
localConnectionString so lunora dev connects to your DB directly (no edge
proxy locally):
{
"hyperdrive": [
{
"binding": "HYPERDRIVE",
"id": "<the id from the command above>",
"localConnectionString": "postgres://user:pass@localhost:5432/db", // gitleaks:allow -- placeholder, not a real secret
},
],
}
Lunora validates this binding (errors if binding is missing; warns if id is
empty/placeholder — it can't connect), but it does not auto-write the id:
that's a remote resource only wrangler hyperdrive create can mint. Importing
@lunora/hyperdrive surfaces a hint reminding you to add it.
Step 3: Regenerate types
lunora codegen
When codegen sees ctx.sql used, it adds sql: SqlClient to ActionCtx
only — never QueryCtx/MutationCtx — with a JSDoc restating the
determinism/realtime caveat.
Step 4: Use ctx.sql from an action
import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
import postgres from "postgres";
import { action, v } from "@lunora/server";
export const listLegacyOrders = action({
args: { orgId: v.string() },
handler: async (ctx, { orgId }) => {
const { connectionString } = createHyperdrive(ctx.env.HYPERDRIVE);
ctx.sql = fromPostgresJs(postgres(connectionString));
return ctx.sql.query<{ id: string; total: number }>(
"select id, total from orders where org = $1",
[orgId],
);
},
});
The package never rewrites SQL — use your driver's native placeholders ($1 for
Postgres, ? for MySQL).
Step 5 (optional): Make it reactive via a projection
External writes don't hit the change-feed, so a subscription won't re-fire on a
Postgres change. To make external data reactive, write a projection into a
defineSchema table from the same action — that write is tracked:
const [row] = await ctx.sql.query<{ id: string; total: number }>(
"select id, total from orders where id = $1",
[id],
);
// This write re-runs live queries reading `orders`:
await ctx.runMutation("orders:upsert", { id: row.id, total: row.total });
Common Pitfalls
ctx.sqlin a query/mutation. It's action-only by design; thehyperdrive_outside_actionadvisor lint flags it. Move the SQL into an action.- Expecting external writes to be reactive. They aren't — Lunora can't see
them. Project into a
defineSchematable if you need reactivity. - Placeholder / empty
id. A Hyperdrive binding with no realidcan't connect (Lunora warns). Runwrangler hyperdrive createand paste the id. - Bundling a driver as a hard dependency. Drivers are optional peer deps — install the one you use; don't assume one ships with the package.
- Wrong placeholder syntax.
$1, $2for Postgres drivers,?for mysql2. The package does not rewrite SQL.
Checklist
-
@lunora/hyperdrive+ one driver (postgres/pg/mysql2) installed. -
wrangler hyperdrive createrun;HYPERDRIVEbinding added towrangler.jsoncwith a realidand alocalConnectionStringfor dev. -
lunora codegenrun soctx.sqlis typed onActionCtx. - All
ctx.sqlaccess lives in actions (advisor clean — nohyperdrive_outside_action). - If reactivity is needed, external rows are projected into a
defineSchematable.