Connections and services
Name your databases in tablewalk.json, then add the optional cache, webhook
and email services a deployment uses.
{ "connections": [ { "name": "crm", "url": "${DATABASE_URL}", "writable": true, "pool": { "read": 8 } }, { "name": "shared", "kind": "cache", "url": "valkeys://cache.internal:6379", "auth": { "password": "${VALKEY_PASSWORD}" } }, { "name": "mail", "kind": "email", "url": "smtps://smtp.example.com:465", "from": "CRM <no-reply@example.com>", "auth": { "username": "${SMTP_USER}", "password": "${SMTP_PASSWORD}" } } ], "apps": [{ "dir": "./crm-app", "env": ["REGION"] }]}DATABASE_URL=postgres://crm_app@db.internal/crm npx tablewalk --config tablewalk.jsonWhat you’ll see
Section titled “What you’ll see”- Each database is opened when it is first used, not at start.
- An unset
${VAR}is refused, naming the variable, so a missing secret never becomes an empty password. - A start with no services runs the same; each one adds a capability, never a source of truth.
When to use it
Section titled “When to use it”- Use a config file as soon as there is a password, a second database or an App to serve: it keeps credentials out of the command line.
- Use
--db postgres://…(orDATABASE_URL) instead for one database you are only looking at. - Use a suite file instead when several Apps each need their own connections and secrets in one process.
Connections
Section titled “Connections”Without --config, tablewalk reads tablewalk.json in the working directory,
then ~/.config/tablewalk/connections.json. A database connection takes:
| Key | Does | Default |
|---|---|---|
name, url |
What the browser and Apps call it; postgres://, mysql:// or sqlite:<path>, usually ${DATABASE_URL} |
required |
writable |
Allows writes, checked in every route and tool that writes. Connect as a database role that may write | false |
pool |
{ "read": n, "write": m }, 1 to 64, PostgreSQL and MySQL (pools) |
4 and 4 |
replicas |
Read replicas’ URLs, PostgreSQL and MySQL, up to 8: lists, dashboards and exports read them (replicas) | none |
schemas |
PostgreSQL schemas to read | every user schema |
hidden |
Columns never served: { "staff": ["password_hash"], "*": ["*_token"] } (secrets) |
none |
rows |
false serves the shape only, never a row |
true |
migrations |
{ "dir": "./migrations" } for tablewalk migrate |
none |
Beside connections, the file may list apps (a directory, or
{ "dir", "env" } naming the environment variables that App may read),
mount, basePath (where an App is served)
and analytics (behavior analytics). The file is read
once, at start; a change waits for a restart.
The cache ("kind": "cache")
Section titled “The cache ("kind": "cache")”A Valkey server (or Redis) every instance of a deployment shares. It holds
cached answers for an HTTP connection with "cache": { "ttl": 60 }, and the
rate-limit windows of public forms, sign-in lockouts and the sign-in routes’
own request budget, so instances behind a load balancer spend one budget.
Records are never cached: every instance reads the database, so a write on one
is what the next read on another sees.
url is valkeys://host:6379 (TLS) or valkey://; rediss:// and
redis:// too, and /2 picks a database. auth takes password and, for an
ACL user, username. Plain valkey:// needs "insecure": true. When the
cache does not answer, reads miss and rate limits fall back to the process’s
own count, said on stderr at most once a minute.
Search
Section titled “Search”A contains filter (the list’s search box, a reference picker, the palette)
runs on the database. A search engine is not one of these services: search
providers are packs, Typesense the first (@tablewalk/search-typesense), and
they ship after 1.0. A "kind": "search" entry is refused with a sentence
that says so, and so is an App that declares a resource’s search while no
provider is plugged in.
Webhooks ("kind": "webhook")
Section titled “Webhooks ("kind": "webhook")”Where a command’s effects go after it
commits. url is https (plain http only to loopback); redirects and
link-local addresses are refused. auth is { "bearer": "${TOKEN}" } or
{ "header": "x-signature", "value": "${SECRET}" }.
Each delivery is a JSON POST with an Idempotency-Key that is the same on
every attempt; deduplicate by it. A 2xx is delivered; anything else is
retried with backoff (1 s doubling to five minutes, eight attempts), then waits for an
operator (redrive). Provision the
delivery store once, with the server stopped:
provisionStores({ postgres: url }, { app: { id, commands } }) from tablewalk/maintenance.
Email ("kind": "email")
Section titled “Email ("kind": "email")”The one service mail goes through: smtps:// (or smtp:// with STARTTLS),
from, and auth; or log: to write each message to the log instead. What
an App sends, and when: email. A tenant App’s mail in and out
adds inbound, dkim, domains, links, spf and returnPath
(tenant mail).
OAuth ("kind": "oauth")
Section titled “OAuth ("kind": "oauth")”Lets a person signed in to an App give an agent a token of their own, through the browser, instead of asking the operator for one:
{ "name": "agents", "kind": "oauth", "apps": ["compass"], "roles": ["sales"] }apps names the Apps it issues for; roles caps what a token may carry
(absent: the App’s api.roles); redirectHosts limits where a client may
send people back. In a tenant App each token is one membership’s, never a
platform-realm role’s. It needs --api-reads, a provisioned token store and an
https address, and is not served in a suite. What a client sees:
the App API.
Common mistakes
Section titled “Common mistakes”Connection references $SMTP_PASSWORD, which is not set→ export it, or set it in the container’s environment; the file never holds the value.- A credential written in the
url→ move it toauthas a${VAR};authvalues are never logged. - Two instances each enforce their own rate limit → name a cache connection so they share one budget.
Related
Section titled “Related”- Build and deploy: containers, load balancers and where an App is served.
- Performance and pools: sizing
poolagainstmax_connections. - Secrets and hidden columns: what
hiddenandenvprotect. - Identity providers: the
"kind": "oidc"and"kind": "saml"connections, and"kind": "scim"provisioning. - App API: the
"kind": "oauth"authorization server, as an agent meets it.