Client SDKs
The generated REST client SDKs — TypeScript, Python, Rust, and Go. Typed CRUD over the REST API with a matching ListResult envelope, per-model Create inputs, and typed errors. Pick your ecosystem.
forgedb generate <node|python|rust|go> --sdk emits a typed REST client that mirrors the
generated REST contract exactly — the same methods and shapes in every language, because
each one wraps the identical HTTP API. node covers Node.js and Bun (they share the
generated TypeScript client). Pick your ecosystem with the toggle above; the .forge schema
and the REST contract underneath never change.
forgedb generate node --sdk # → generated/types.ts (bun --sdk is equivalent)forgedb generate python --sdk # → generated/python-sdk/forgedb_client.pyforgedb generate rust --sdk # → generated/rust-sdk/ (a reqwest client crate)forgedb generate go --sdk # → generated/go-sdk/client.goCRUD methods#
Every SDK exposes a single client with flat, model-suffixed methods — there is no
per-model sub-object. For a model Post, each maps one-to-one onto a REST call:
| Operation | REST call | Returns |
|---|---|---|
| get | GET /api/post/{id} | the record, or a null / None / nil miss on 404 |
| list | GET /api/post | a ListResult envelope |
| create | POST /api/post | the new id (typed error on 409/422) |
| update | PUT /api/post/{id} | true, or false on 404 |
| delete | DELETE /api/post/{id} | true, or false on 404 |
The method names follow each language's convention:
camelCase, verb-prefixed — getPost / listPost / createPost / updatePost /
deletePost — on a ForgeDBClient:
import { ForgeDBClient } from "./generated/types";
const db = new ForgeDBClient("http://localhost:3000");
const { data, total } = await db.listPost({ limit: 20, sort: "views", order: "desc" });
const id = await db.createPost({ title: "Hello", views: 0 });
const post = await db.getPost(id); // Post | null
await db.updatePost(id, { ...post!, views: 1 });
await db.deletePost(id); // booleansnake_case, verb-prefixed — get_post / list_post / create_post / update_post /
delete_post — on a ForgeDbClient (stdlib urllib, no runtime deps):
from forgedb_client import ForgeDbClient, PostCreate, ListOptions
db = ForgeDbClient("http://localhost:3000")
page = db.list_post(ListOptions(limit=20, sort="views", order="desc"))
post_id = db.create_post(PostCreate(title="Hello", views=0))
post = db.get_post(post_id) # Post | None
db.delete_post(post_id) # boolsnake_case, verb-prefixed, every method async returning Result<_, ForgeDbError> —
get_post / list_post / create_post / update_post / delete_post — on a
ForgeDbClient (a reqwest crate):
use forgedb_client::{ForgeDbClient, PostCreate, ListOptions, SortOrder};
let db = ForgeDbClient::new("http://localhost:3000");
let page = db.list_post(&ListOptions {
sort: Some("views".into()),
order: Some(SortOrder::Desc),
limit: Some(20),
..Default::default()
}).await?;
let id = db.create_post(&PostCreate { title: "Hello".into(), views: 0, ..Default::default() }).await?;
let post = db.get_post(&id).await?; // Option<Post>
db.delete_post(&id).await?; // boolPascalCase, verb-prefixed, each returning (value, error) — GetPost / ListPost /
CreatePost / UpdatePost / DeletePost — on a Client from package forgedbclient
(net/http, no external modules):
db := forgedbclient.NewClient("http://localhost:3000")
limit := 20
page, err := db.ListPost(&forgedbclient.ListOptions{Sort: "views", Order: "desc", Limit: &limit})
id, err := db.CreatePost(&forgedbclient.PostCreate{Title: "Hello", Views: 0})
post, err := db.GetPost(id) // (*Post, error); nil on 404
ok, err := db.DeletePost(id) // (bool, error)The list envelope#
list<Model> returns a paginated envelope matching the REST list shape — data, total
(the match count before pagination), limit, and offset:
interface ListResult<T> { data: T[]; total: number; limit: number; offset: number; }@dataclass
class ListResult(Generic[T]):
data: List[T]
total: int
limit: int
offset: intpub struct ListResult<T> { pub data: Vec<T>, pub total: usize, pub limit: usize, pub offset: usize }type ListResult[T any] struct { Data []T; Total int; Limit int; Offset int }The list options accept pagination (limit/offset), sort + order (asc/desc), and
a per-field exact-match filter, mapped onto the REST query parameters (see
Generated REST API).
Create inputs#
Each model gets a create-input type that omits the server-assigned id; create<Model>
takes it and returns the new id (the REST create responds { id }, not the full record):
type PostCreate = Omit<Post, "id">;PostCreate — a @dataclass mirroring Post without id.
PostCreate — a serde struct mirroring Post without id; it derives Default, so
..Default::default() fills any field you omit.
PostCreate — a struct mirroring Post without id.
Typed errors#
A write failure surfaces as a typed error carrying the HTTP status and the parsed body,
so you can branch on 409 (unique / dangling-FK conflict) versus 422 (field constraint).
A get / update / delete 404 maps to a null / None / false result instead of raising.
try {
await db.createPost(input);
} catch (e) {
if (e instanceof ForgeDBError && e.status === 422) {
// validation failure — inspect e.body
}
}ForgeDbError(status, message, body) — raised on any non-2xx write; catch it and branch on
.status.
ForgeDbError { status, message, body } — the Err arm of every method's Result; match on
.status.
*ForgeDbError{Status, Message, Body} — the returned error on any non-2xx write;
type-assert it to read .Status.
Vendoring & publishing#
The SDK is generated code you vendor into your app, not a registry dependency — regenerating overwrites the client but never the packaging file next to it:
generate node --sdk also emits package.json + tsconfig.json next to types.ts, only
if absent — regenerating (even with --force) never clobbers them, so your package metadata
is safe to edit. The output is strict tsc --noEmit clean and npm-publishable.
generate python --sdk emits forgedb_client.py + a pyproject.toml under python-sdk/.
The client is pure stdlib urllib — no runtime dependencies to install.
generate rust --sdk emits a self-contained crate under rust-sdk/ (its own Cargo.toml).
Add it as a path or git dependency; it pulls reqwest + serde.
generate go --sdk emits client.go + a go.mod under go-sdk/. Pure net/http +
encoding/json — no external modules, no cgo.
Honest limits (all four SDKs)
Ids are typed as a string uniformly (URL paths are strings — integer-PK callers pass the
stringified value); create returns the id, not the full record; and there is no WebSocket
/ subscription client yet — every SDK is REST-only. For realtime, use the raw
WebSocket routes directly.
Related#
- Generated REST API — the wire contract every SDK mirrors.
- Quickstart — the SDK in the full init → generate → build loop.