Developer

Database (projectDb)

Raw per-resource SQLite, the lower-level primitive that typed entities are built on.

ctx.sdk.projectDb is the project-tier SQLite primitive: a per-resource SQLite database stored alongside the resource in the project folder. It is the Rust primitive that typed entities ride on, so reach for defineEntity first. Use projectDb directly only when you need a raw SQL escape outside the entity API, which most applets will not.

Scope required: projectDb:read / projectDb:write

Usage

const rows = await ctx.sdk.projectDb.query<Article>(
  "SELECT * FROM articles WHERE feed_id = ? ORDER BY pub_date DESC LIMIT 50",
  [feedId]
);
await ctx.sdk.projectDb.execute(
  "UPDATE feeds SET title = ? WHERE id = ?",
  [title, id]
);
await ctx.sdk.projectDb.batch([
  { sql: "INSERT INTO articles VALUES (?,?,?)", params: [...] },
  { sql: "UPDATE feeds SET unread_count = unread_count + 1 WHERE id = ?", params: [feedId] },
]);
await ctx.sdk.projectDb.migrate([
  { id: "001-init", sql: SCHEMA_SQL },
]);

The database lives at {project_folder}/{resource folderPath}/{filename}. The path resolves through cmd_get_resource(resourceId).folderPath.

Prefer typed entities

For almost everything, declare a typed entity with defineEntity instead of writing raw SQL. Entities are lint-clean, and their schema history covers migrations for you. projectDb is the lower-level surface those entities are built on; drop to it only for queries the entity API cannot express. See Entities.

Manifest configuration

{
  "scopes": [
    "projectDb:read",
    "projectDb:write"
  ]
}

Notes

  • The sandbox is strict: .. is rejected, and symlinks are rejected on canonicalization.
  • One database per resource. The path is keyed to the resource’s folderPath.
  • Use parameterized queries (?) and never concatenate input into SQL.
  • migrate runs ordered, id-keyed migrations so a resource opened multiple times stays consistent.
  • Reach for defineEntity before this primitive; raw SQL is the escape hatch, not the default.