Skip to content
Go To Dashboard

Data

Data lets a deployed agent provision a time-limited PostgreSQL database and use it through a standard Postgres client. Agent steps call the typed ctx.sapiom.database capability; Sapiom supplies the authenticated cloud connection when the deployed agent runs.

This is a provisioning capability, not a SQL query API. database.create returns connection credentials; the agent project supplies the database client and runs its own SQL.

What the agent needsRecommended operation
A new ephemeral Postgres databasedatabase.create({ duration })
A stable key for a database reused by later stepsCreate with handle, then database.get(handle)
Active and provisioning databasesdatabase.list()
Early cleanup before automatic expirydatabase.delete(idOrHandle)
Queries, migrations, or transactionsA standard Postgres client using connectionString

Choose a lifetime from "15m", "1h", "4h", "24h", or "7d". Use the shortest lifetime that safely contains the work.

This example assumes the agent project includes pg:

import { Client } from "pg";
const db = await ctx.sapiom.database.create({
duration: "1h",
name: "report-build",
});
if (db.status !== "active" || !db.connection) {
throw new Error(`Database ${db.id} is ${db.status}`);
}
const client = new Client({
connectionString: db.connection.connectionString,
});
try {
await client.connect();
await client.query(
"create table report_rows (label text not null, value integer not null)",
);
await client.query(
"insert into report_rows (label, value) values ($1, $2)",
[input.label, input.value],
);
const result = await client.query(
"select label, value from report_rows order by label",
);
return { rows: result.rows };
} finally {
await client.end().catch(() => {});
await ctx.sapiom.database.delete(db.id).catch((error) => {
ctx.logger.warn("database cleanup failed", {
databaseId: db.id,
error: String(error),
});
});
}

Use parameterized queries for values. Never construct SQL by interpolating caller input, and never return or log connectionString, username, or password.

A handle is a tenant-scoped, human-friendly lookup key. Use one only when another step or run must find the same database before it expires:

const db = await ctx.sapiom.database.get(input.databaseHandle);
if (db.status !== "active" || !db.connection) {
throw new Error(`Database ${input.databaseHandle} is ${db.status}`);
}

Pass the handle or database ID between steps, not the connection credentials. A database can be provisioning with connection: null, and it can expire between lookup and connection, so handle both conditions.

Local Run replaces ctx.sapiom.database calls with deterministic records. It does not provision Postgres. The built-in connection points at fixture localhost credentials; a direct pg client remains real and can attempt a local connection.

Test provisioning and lifecycle branches with exact capability fixtures, and put SQL access behind an injectable adapter when the Local Run must exercise query-dependent logic:

{
"version": 1,
"steps": {
"prepare-data": {
"database.create": {
"id": "db-local",
"handle": null,
"name": "report-build",
"description": null,
"status": "active",
"region": "us-east-1",
"pgVersion": 17,
"duration": "1h",
"connection": {
"connectionString": "postgresql://fixture.invalid/report"
},
"expiresAt": "2099-01-01T00:00:00.000Z",
"createdAt": "2098-12-31T23:00:00.000Z"
}
}
}
}

The override is returned verbatim. Supply the shape the step consumes, assert the terminal output, and require both unusedStubs and stubWarnings to be empty. Use a small production run to verify real provisioning, client connectivity, SQL behavior, and cleanup.

Automatic expiry is the final cleanup boundary, not a reason to leave scratch databases running. Delete a database when the work finishes, use list() to find resources left by an interrupted run, and make retry behavior explicit when a stable handle might already exist.

Use the signed-in capability catalog for current regions, limits, and pricing. Database usage depends on the selected lifetime and live resource lifecycle; do not copy a rate into agent logic.