Api
lib/api (kalt-lib-api) creates the HTTP methods and offers them to a service. get, post, put, patch, and delete build a route. serve and serve_after listen, and they register that list on the gateway. app/web/app/lib/ok-fetch.ts is the browser client. Both speak ApiResult<T> from the contracts crate.
HTTP handlers never write Postgres. They publish onto that service's public channel with ch.pub; the same binary's worker applies what ch.sub reads. The channel's model, owner, and subscribers are in Channels. The publish check is part of ch.pub. The channels process stores the entry.
Service
pub fn router() -> kalt_lib_api::Api {
kalt_lib_api::Api::new()
.route(
"/api/publications",
kalt_lib_api::get(list_or_get).post(create),
)
.route("/api/publications/{id}/title", kalt_lib_api::put(put_title))
}
kalt_lib_api::serve_after(
"publications",
kalt_lib_api::Model::Domain,
"0.0.0.0:8081",
http::router(),
kalt_db::migrations!(),
|state| {
let worker = state.clone();
state.ch.follow("publications-1", move |event| {
let state = worker.clone();
async move { apply::handle(state, event).await }
});
},
)
.await
serve writes the method list to Redis at kalt.gw.methods:{service} and adds the service to kalt.gw.services. The gateway reads that list. A request matches a registered verb and path before it uses the ok.yaml prefix table. public() on the methods just added leaves them open. open() does that for every method added so far, which is how auth and geo stay public. lead_deny() rejects a lead on the methods just added. lead_deny_all() does that for every method added so far.
AppState::connect loads {SERVICE}_DATABASE_URL (or DATABASE_URL), REDIS_URL, and AUTH_JWT_SECRET, runs the migrations the service passed, and binds Channel::public(service, model): the public stream kalt.{service} and the owner group kalt-{service}. Ch::follow in Channels is the subscribe loop, started from the service main.
Extractors:
OptionalUser— Bearer present and valid, orNone.SessionUser— signed in.EditorUser—role == "editor".MsgCtx—X-Msg-SourceandX-Trace-Id.
emit_domain calls ch.pub with type=domain on that public channel. A source channel, such as int-github, publishes type=source through the same envelope. The channels process stores both. wait_exists polls until the worker has written the row. ok(data) wraps { data, success: true, error: null }. Failures are ApiFail with unauthorized, not_editor, invalid, not_found, conflict, rate_limited, or server_error.
Frontend
bindFetch(import.meta.url) stamps the calling file as X-Msg-Source and attaches the session traceId. Auth adds Authorization: Bearer except on public /api/auth and /api/geo paths.
import { bindFetch } from '~/lib/ok-fetch'
const okFetch = bindFetch(import.meta.url)
const result = await okFetch<Publication>('/api/publication/ledger/AbC12~_x')
if (!result.success) return
fieldWrite(value) is { tz, value } for ledger PATCHes. In production, okFetch sends /api to the gateway.