Sorbus (TypeScript)
Sorbus is the typed TypeScript client, purpose-built for Apiwork. It reads the generated contract and gives you typed API calls — filtering, sorting, pagination, nested writes, and error handling — with zero configuration.
You describe your domain in Ruby. Apiwork generates a contract. Sorbus turns it into a typed client. The database is the source of truth, and every change flows from Rails to TypeScript automatically.
Setup
Generate the contract:
Apiwork::API.define '/api/v1' do
export :sorbus do
key_format :camel
end
endCreate the client:
import { createClient } from 'sorbus';
import { contract } from './contract';
const api = createClient(contract, '/api/v1', {
headers: () => ({
Authorization: `Bearer ${getToken()}`,
}),
serializeKey: 'snake',
normalizeKey: 'camel',
});That's it. Every endpoint in the contract is now a typed function on api.
Queries
const { invoices, meta } = await api.invoices.index({
filter: {
status: {
eq: 'paid',
},
issuedOn: {
gte: '2024-01-01',
},
},
sort: {
issuedOn: 'desc',
},
page: {
number: 1,
size: 20,
},
});Every filter operator, enum value, and sort field is typed. Invalid filters fail at compile time — not in production.
Logical Operators
Combine filters with AND, OR, and NOT:
const { invoices } = await api.invoices.index({
filter: {
OR: [
{
status: {
eq: 'draft',
},
},
{
status: {
eq: 'overdue',
},
},
],
},
});Includes
Load associations with typed include:
const { invoice } = await api.invoices.show({
id: '123',
include: {
customer: true,
items: true,
},
});
// invoice.customer and invoice.items are typedMutations
Create
const { invoice } = await api.invoices.create({
invoice: {
number: 'INV-001',
issuedOn: '2024-06-01',
items: [
{
description: 'Consulting',
quantity: 10,
rate: 150,
},
],
},
});Update with Nested Writes
Create, update, and delete related records in a single request:
const { invoice } = await api.invoices.update({
id: '123',
invoice: {
items: [
{
OP: 'update',
id: '5',
description: 'Updated item',
},
{
OP: 'create',
description: 'New item',
quantity: 1,
rate: 100,
},
{
OP: 'delete',
id: '3',
},
],
},
});OP determines the operation: 'create', 'update', or 'delete'.
Error Handling
Sorbus throws on non-OK responses by default. Catch specific status codes to get typed error data instead:
const result = await api.invoices.create(
{
invoice: {
number: 'INV-001',
},
},
{
catch: [422],
},
);
if (!result.ok) {
result.data.errors;
// { number?: string[], issuedOn?: string[] }
return;
}
result.data.invoice; // fully typedThe error shape is generated from the contract. Rails validation errors map directly to typed fields.
The Workflow
1. Change your Rails code (add column, change type, add enum value)
2. Regenerate the contract
3. TypeScript tells you what brokeThe database is the source of truth. Apiwork reads it. Sorbus types it. Nothing drifts.
See also
- Sorbus documentation — full client reference
- Sorbus export — export configuration and output format