Backend runtime contract

What your app's backend can rely on at runtime: the database role, transactions, timeouts and size limits, each marked supported, current behavior or not provided.

This page lists what your app's backend endpoints, background tasks and scheduled jobs can rely on: how they connect to the database, what a transaction means, which timeouts exist and the size limits. Anything not on this page is not part of the contract.

Supported
You can build on it. Floot keeps it, and a change is announced in the changelog before it ships.
Current behavior
True today, but it may change without notice. Fine for performance, but your code should not depend on it to be correct.
Not provided
Floot does not offer it. The details say what to do instead.

Database connection

ItemStatusDetails
Connection stringSupportedYour backend reads it from FLOOT_DATABASE_URL. It is a direct connection to Postgres with no connection pooler in between, and the preview and the published app use the same database.
Database roleSupportedThe app connects as the database's owner role (neondb_owner for databases Floot creates). That role owns the tables your app creates, so it can read, write and change all of them. Run select current_user to see it.
A separate, restricted login for the appNot providedThere is one role for everything: your app, the Data tab and your assistant's SQL tools.
Row-level securityNot providedPolicies don't apply to a table's owner, which is the role the app uses. Whether the role also has BYPASSRLS depends on when the database was created: select rolbypassrls from pg_roles where rolname = current_user tells you. Check who may see a row in your endpoint code, not with policies.
Session state across requestsNot providedConnections are opened when a request needs them and closed when it ends. A plain SET, a temporary table, a prepared statement or a session-level advisory lock doesn't carry over to the next request.
ReconnectingCurrent behaviorWhen the database is waking up or a connection drops, Floot's database client opens a new connection and tries again, up to 4 attempts with a 30-second connect timeout each. A query outside a transaction that failed because the connection was lost can be sent again. Statements inside a transaction are never sent again (see below).

Transactions

ItemStatusDetails
No outer transactionSupportedFloot never wraps a request, task or scheduled job in a transaction. Your own BEGIN is the top-level transaction, and a statement outside one commits on its own as soon as it succeeds.
A transaction that resolvesSupportedA transaction runs on one connection from start to finish. await db.transaction().execute(fn) resolves only after Postgres has answered your COMMIT. When your callback returns, nothing is committed yet; the promise is what counts.
No replaySupportedFloot doesn't run your transaction callback again or send COMMIT a second time. The only retry is opening the transaction's connection, before your callback has started. Floot never sends an HTTP request to your backend a second time either.
A connection lost during COMMITNot providedYour code gets an error and Floot can't tell you whether the commit went through. For writes that must happen exactly once, give each one a unique key, so you can check afterwards and a second attempt is refused.
Background task retriesCurrent behaviorA task or job that throws, or that was cut off at its time limit, is not started again by Floot. The exception is a database connection failure before any of the task's work reached the database or the network.

Database timeouts

Floot sets no statement_timeout or lock_timeout on your database, so a query can wait for a lock or run until the request's own time limit. To bound them, set them yourself as the first statements of the transaction:

BEGIN;
SET LOCAL lock_timeout = '1s';
SET LOCAL statement_timeout = '3s';
-- your queries
COMMIT;
  • This is supported: Postgres itself enforces both and cancels the statement on the server, and because the connection is direct, they apply to exactly that transaction and end with it.
  • With Kysely, run both through Kysely's sql tag on trx, as the first thing in your db.transaction().execute(async (trx) => …) callback.
  • A plain SET (without LOCAL) lasts only until the connection closes at the end of the request, so it isn't a reliable way to set a limit.
  • ALTER ROLE … SET statement_timeout would apply to everything that uses the role, including the Data tab and your assistant's SQL tools, so Floot doesn't recommend it.

Run time, memory and size limits

LimitStatusValue
Background task or scheduled jobSupportedUp to 15 minutes. A task is stopped about 30 seconds before that, so it can hand off what's left to a new task.
Request from a browser or another serviceSupportedIt must start answering within 2 minutes and never go quiet for longer than that. Otherwise the caller gets a 504 error, even though your code may keep running for up to 15 minutes. For longer work, queue a background task and answer at once.
MemorySupported1 GB by default (2 GB with server-side rendering). Ask your assistant to set anything from 512 MB to 4 GB.
Request bodySupportedAbout 6 MB. For bigger uploads, send the file to storage and pass its key to your endpoint.
Response bodyCurrent behaviorResponses are streamed, so they can be larger than the request limit: up to 200 MB.
Your backend's own outgoing requestsCurrent behaviorFloot puts no size or time limit on calls your backend makes, other than the memory and run time above. A download read as a stream (for example for await over response.body) is never held in memory whole, so you can hash it piece by piece.
Cancelling an outgoing requestNot providedAbortController / AbortSignal work as they do in Node.js. Floot adds no signal confirming that the other server stopped. When a request or task ends, connections still open are closed.

Not part of the contract

  • A release ID or lockfile for a publish. Each publish installs your dependencies again from the versions in package.json.
  • The typecheck environment. The compiler settings and the platform's type declarations are not published. Your assistant's typecheck tool returns the errors it finds, nothing more.
  • A per-project hard spending cap. The daily hosting limit pauses published apps and is based on an estimate, so a day can end slightly above it.