Skip to main content
Every generated app keeps its data in a real SQL database of its own. One tool reaches it, and two table namespaces are the entire permission model.

One tool, one statement

vendo_apps_sql runs one SQL statement against the app’s own database and answers with { columns, rows, rowCount }. A statement is one of SELECT, WITH, INSERT, UPDATE, DELETE, CREATE TABLE, ALTER TABLE, or DROP TABLE. Session, role, schema, and catalog verbs are refused. The tool’s own description states the live dialect, Postgres or SQLite, because generated SQL is written for one. SQL that sticks to the common subset (TEXT, INTEGER, REAL, PRIMARY KEY, ordinary joins) travels between the two; a vendor-specific type or function does not.

Two namespaces, no third

A bare table name is refused with what happened, why, and the fix:
refusal

mine. is a separate table per person

mine.notes is not one table filtered per caller. Each person gets their own physical table, so ordinary SQL keeps its ordinary meaning:
  • A PRIMARY KEY is unique per person, not across the app.
  • A UNIQUE constraint is per person.
  • Delete-then-write-by-key statements have no reachable target in anyone else’s rows.
Schema changes that touch mine. are recorded once and replayed for each user who writes, so every person’s copy has the same shape. Reading a mine. table you have never written answers empty rather than creating anything. The fence is enforced at the tool, by name resolution, never by generated SQL and never by a database privilege. mine.x and shared.x are the only addresses that exist; the physical names they resolve to cannot be written or guessed from inside a statement. Generated SQL never scopes anything and cannot.

Inside a screen

A screen loads its own rows with useQuery. A SELECT grades as a read per call, so the tool is queryable even though its authored risk is write.
app.tsx
Writes go through tools.vendo_apps_sql(…) from a handler, like every other write a screen makes.

Where the database lives

createVendo({ appDatabase }) is the adapter slot. Unset with a store wired, every app gets its own fenced schema inside that store’s Postgres, and there is nothing to configure.
vendo.ts
A store with no SQL behind it composes no adapter, and the tool is not offered. A call that reaches a deployment without one answers unavailable, naming the three ways to get a database: pass a store whose Postgres backs every app, pass appDatabase yourself, or set VENDO_API_KEY. Passing your own AppDatabase adapter always wins. The adapter executes and decides nothing: every rule that makes mine. one person’s rows lives above the adapter seam, so two implementations cannot disagree about who sees what.

Migrating from the app-data tools

The app-data family is gone, replaced whole by the SQL database: Nothing carries records over automatically. An app that kept data in the old collections recreates it as shared. or mine. tables on its next edit. Calls left on the old appData.* wire operations answer not-implemented (HTTP 501) naming the operation, so a stale integration fails loudly instead of silently.