Browse topics
Database API (SQLite)
Per-resource SQLite database for structured data storage.
Each resource gets its own SQLite database for structured data storage. The database is created automatically on first use - just start executing SQL.
Capability required: database:resource (plain) or database:resource:encrypted (SQLCipher encrypted)
Frontend (iframe)
import { createResourceClient } from "@rightplace/sdk";
const rp = createResourceClient();
await rp.ready();
// Create a table
await rp.db.execute(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at INTEGER DEFAULT (unixepoch())
)
`);
// Insert data with parameters
await rp.db.execute(
"INSERT INTO notes (title, content) VALUES (?1, ?2)",
["My First Note", "Hello world!"]
);
// Query data
const notes = await rp.db.query("SELECT * FROM notes ORDER BY created_at DESC");
// → [{ id: 1, title: "My First Note", content: "Hello world!", created_at: 1713250000 }]
// Query with parameters
const results = await rp.db.query(
"SELECT * FROM notes WHERE title LIKE ?1",
["%First%"]
);
// Update
const result = await rp.db.execute(
"UPDATE notes SET content = ?1 WHERE id = ?2",
["Updated content", 1]
);
console.log(result.rowsAffected); // 1
// Delete
await rp.db.execute("DELETE FROM notes WHERE id = ?1", [1]);
Backend (Node.js)
import { createResourceServer } from "@rightplace/sdk/server";
const server = createResourceServer({
onInit: async ({ rp }) => {
await rp.db.execute(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL,
in_stock INTEGER DEFAULT 1
)
`);
},
methods: {
addItem: async (params, { rp }) => {
const result = await rp.db.execute(
"INSERT INTO items (name, price) VALUES (?1, ?2)",
[params.name, params.price]
);
return { id: result.lastInsertId };
},
getItems: async (_params, { rp }) => {
return rp.db.query("SELECT * FROM items WHERE in_stock = 1 ORDER BY name");
},
},
});
server.start();
MCP
The same SQLite store is reachable from agents through three MCP tools. All of them take a resourceId so the call is scoped to one resource’s database.
| Tool | Purpose | Permission |
|---|---|---|
rightplace_db_query | Run a SELECT and return rows | database:read |
rightplace_db_execute | Run INSERT / UPDATE / DELETE / DDL | database:write |
rightplace_db_list_tables | List tables + column schema | database:read |
{
"name": "rightplace_db_query",
"arguments": {
"resourceId": "res_abc",
"sql": "SELECT id, title FROM notes WHERE title LIKE ?1",
"params": ["%hello%"]
}
}
RobinPath Bridge
From a script:
# DDL
rightplace.db_execute {
resourceId: "res_abc",
sql: "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT, body TEXT)"
}
# Write
rightplace.db_execute {
resourceId: "res_abc",
sql: "INSERT INTO notes (title, body) VALUES (?1, ?2)",
params: ["hello", "world"]
} into $res
log "inserted id:" $res.lastInsertId
# Read
rightplace.db_query {
resourceId: "res_abc",
sql: "SELECT id, title FROM notes ORDER BY id DESC LIMIT ?1",
params: [10]
} into $rows
for $r in $rows
log $r.id $r.title
endfor
# Schema discovery
rightplace.db_list_tables {resourceId: "res_abc"} into $tables
API Reference
rp.db.execute(sql, params?)
Execute a SQL statement (INSERT, UPDATE, DELETE, CREATE TABLE, etc.).
| Parameter | Type | Description |
|---|---|---|
sql | string | SQL statement |
params | unknown[] (optional) | Positional parameters (?1, ?2, …) |
| Returns | Promise<{ rowsAffected: number, lastInsertId: number }> |
rp.db.query(sql, params?)
Execute a SELECT query and return rows.
| Parameter | Type | Description |
|---|---|---|
sql | string | SQL SELECT statement |
params | unknown[] (optional) | Positional parameters (?1, ?2, …) |
| Returns | Promise<Record<string, unknown>[]> | Array of row objects |
Each row is an object with column names as keys.
Parameter Types
| JavaScript Type | SQLite Type |
|---|---|
string | TEXT |
number (integer) | INTEGER |
number (float) | REAL |
boolean | INTEGER (1 or 0) |
null | NULL |
Encrypted Databases
Use the database:resource:encrypted capability for sensitive data. The database is encrypted with SQLCipher using the user’s encryption key.
{
"capabilities": [
"database:resource:encrypted"
]
}
The API is identical - encryption is transparent. The only difference is that the database file on disk is encrypted and cannot be opened with standard SQLite tools.
Plain Databases
Use database:resource for non-sensitive data. The database is a standard SQLite file.
{
"capabilities": [
"database:resource"
]
}
Storage Location
The database is stored at:
~/.rightplace/{user_id}/resources/{manifest_id}/resource.db
One database per resource. Use tables for logical separation. The database persists across app restarts and resource updates. It is deleted when the resource is uninstalled.
Tips
- Use
CREATE TABLE IF NOT EXISTS- your resource may be opened multiple times - Use parameterized queries (
?1,?2) - never concatenate user input into SQL - Use
INTEGER PRIMARY KEY AUTOINCREMENTfor auto-incrementing IDs - SQLite supports JSON functions:
json(),json_extract(),json_array() - Use
unixepoch()for timestamps