Config & field DSL
Field DSL
"<type>[ flag]... [ = default]"
- types:
stringtextslugemailintegerbooleandatetimejsonfileimageenum(a,b,c)ref:<resource> - flags:
requireduniqueondelete=cascade|setnull|restrict(ref only) - default:
= valueโ= nowfills adatetimeat insert;= now!also re-stamps it on every update (anupdated_at)
Changing fields against a live database: an added field becomes a column in place, but nullable and without its unique constraint โ SQLite's ALTER TABLE ADD COLUMN cannot carry one, and nothing above the database enforces uniqueness, so a unique field added to a table that already exists accepts duplicates where the same config on a fresh database returns 409. A removed or renamed field leaves its old column behind, named in a startup warning and never dropped โ a rename still needs a real migration: hand-written SQL, run against the database outside septic, the way pooppress owns its own. boolean is stored as SQLite 0/1 and comes back from the API as true/false. A datetime is stored and read as UTC: a value with no offset (what a datetime-local input posts) is taken as UTC so it round-trips unchanged, and an explicit offset (โฆZ, โฆ+02:00) is honoured.
Resource options
"posts": {
"methods": ["GET", "POST", "PUT", "DELETE"],
"access": { "read": "public", "write": ["editor", "admin"] },
"fieldAccess": { "status": { "write": ["editor", "admin"] } },
"unique": [{ "columns": ["collection", "slug"], "coalesce": { "collection": 0 } }],
"indexes": [["status", "published_at"]],
"fields": { "โฆ": "โฆ" }
}
- access โ
"public", a role, or a list of roles;adminpasses everything. - fieldAccess โ who may set a given field (an author submitting
status=publishedjust can't). - unique โ composite; the
coalesceform makes a NULL a sentinel so null-key rows still collide. - indexes โ secondary indexes.
Querying a list
| Param | Effect |
|---|---|
?limit=&offset= |
paginate โ 50 by default, capped at 200 |
?sort=<col>&order=asc|desc |
order by a column |
?<col>=value |
equality filter โ a boolean column takes true/false as well as 1/0; a repeated or bracketed key (?a=1&a=2, ?a[x]=1) is a 422, not a 500 |
?expand=<refField> |
inline a ref: field's referenced row โ the target must be a configured resource and the caller must pass its access.read |
Column names are checked against the schema; unknown params are ignored. Responses carry the id plus the declared fields โ an undeclared column (like password_hash on a served users table) never leaves the database.
Users & auth
septic owns a users table (email, role, password_hash). A users resource in config extends it with your own columns (display_name, avatar_url, โฆ). Sessions are stateless signed cookies โ set SEPTIC_SECRET in production. The cookie is HttpOnly; SameSite=Lax, and gains Secure under NODE_ENV=production (or auth.secureCookies: true), so the token never rides a plain-http request.