Documentation Index
Fetch the complete documentation index at: https://docs.abloatai.com/llms.txt
Use this file to discover all available pages before exploring further.
This page covers the SDK behavior around options, errors, retries, and runtimes.
Constructor
import Ablo from '@abloatai/ablo';
import { defineSchema, model, z } from '@abloatai/ablo/schema';
const schema = defineSchema({
tasks: model({
title: z.string(),
status: z.enum(['todo', 'doing', 'done']),
}),
});
const ablo = Ablo({
schema,
apiKey: process.env.ABLO_API_KEY,
});
Common options:
| Option | Purpose |
|---|
schema | Required for typed model resources. Omit only for advanced schema-less runtimes. |
apiKey | Bearer credential for trusted server runtimes. Defaults to ABLO_API_KEY when available. |
baseURL | Override the hosted sync endpoint for staging or private deployments. |
persistence | volatile by default. Use indexeddb for browser durable cache and offline queueing. |
fetch | Custom fetch implementation for tests or non-standard runtimes. |
defaultHeaders | Extra headers attached to every HTTP request. |
defaultQuery | Extra query parameters attached to every HTTP request. |
dangerouslyAllowBrowser | Required before sending an API key from browser code. Prefer a server route instead. |
There is intentionally no databaseURL constructor option. Teams that keep
canonical rows in their own database use a signed Data Source
endpoint.
Model Methods
Each schema model becomes a typed resource:
await ablo.ready();
const [task] = await ablo.tasks.load({ where: { id: 'task_123' } });
const local = ablo.tasks.retrieve('task_123');
await ablo.tasks.create({ title: 'Draft launch plan', status: 'todo' });
await ablo.tasks.update('task_123', { status: 'done' }, { wait: 'confirmed' });
await ablo.tasks.delete('task_123', { wait: 'confirmed' });
load is async hydration from local store and server. retrieve, list, and
count are synchronous local reads after data is loaded.
list accepts the same practical read options the React selector path uses:
where, filter, orderBy, limit, offset, and scope. Scope defaults to
'live'; pass 'archived' or 'all' when you intentionally want non-live
rows.
Multiplayer Behavior
Multiplayer works when every participant uses the same model resource path. A
human Server Action, a browser view, and an agent worker can all use
ablo.tasks:
const [task] = await ablo.tasks.load({ where: { id } });
const snap = ablo.snapshot({ tasks: id });
await ablo.tasks.update(id, patch, {
readAt: snap.stamp,
onStale: 'reject',
wait: 'confirmed',
});
The confirmed write fans out over realtime subscriptions. React clients that use
useAblo((ablo) => ablo.tasks.retrieve(id)) receive the new row, and selectors
such as useAblo((ablo) => ablo.intents.list({ resource: 'tasks', id }))
receive active intents. There is
no extra multiplayer setup beyond routing shared state through Ablo.
If an app writes directly to its database, Ablo cannot coordinate that write
until the app reports it through Data Source events.
Per-Write Options
await ablo.tasks.update(
'task_123',
{ status: 'done' },
{
wait: 'confirmed',
readAt: snap.stamp,
onStale: 'reject',
intent,
idempotencyKey: 'task_123:mark-done:v1',
timeout: 20_000,
},
);
| Option | Purpose |
|---|
wait | queued resolves after local queueing; confirmed waits for server acceptance. |
readAt | State cursor the write was based on. |
onStale | Policy when the target changed after readAt. Prefer reject. |
intent | Active work claim associated with this write. |
idempotencyKey | Stable key for retry-safe writes. The SDK generates one when omitted. |
timeout | Maximum time for the write call. |
Busy Behavior
const busy = ablo.intents.list({ resource: 'tasks', id: 'task_123' });
if (busy.length > 0) {
await ablo.intents.waitFor(
{ resource: 'tasks', id: 'task_123' },
{ timeout: 30_000 },
);
}
Reads never silently block. For raw resource calls, use ifBusy:
return returns active intents.
wait waits for matching intents to clear.
fail throws AbloBusyError.
Schema clients use the realtime stream for waits. Schema-less HTTP clients must
provide busyPollInterval when using ifBusy: 'wait'.
Errors
All SDK errors extend AbloError and carry a stable type.
| Error | Typical cause |
|---|
AbloAuthenticationError | Missing, invalid, or expired credential. |
AbloPermissionError | Valid credential, denied operation or scope. |
AbloRateLimitError | Rate limit or quota exceeded. Check retryAfterSeconds. |
AbloIdempotencyError | Same idempotency key reused with a different request. |
AbloConnectionError | Network, timeout, abort, or transport failure. |
AbloValidationError | Invalid input or unsupported request shape. |
AbloServerError | Server-side 5xx. Retry with backoff if the operation is idempotent. |
AbloStaleContextError | Write was based on stale readAt state. Re-read and retry. |
AbloBusyError | Active intent conflicted with ifBusy: 'fail' or a busy wait timed out. |
import { AbloBusyError } from '@abloatai/ablo';
try {
await ablo.tasks.update('task_123', { status: 'done' }, { wait: 'confirmed' });
} catch (error) {
if (error instanceof AbloBusyError) {
return { status: 'busy', intents: error.intents };
}
throw error;
}
Retries and Idempotency
Model writes are retry-safe by default because the SDK attaches an idempotency
key. If you provide your own key, keep it stable for retries of the same logical
operation and never reuse it for a different payload.
Retry transport failures and 5xx with backoff. Do not blindly retry validation,
permission, idempotency, or stale-context errors without changing the request.
Logging
Pass a logger when you need SDK logs in your own observability pipeline:
const ablo = Ablo({
schema,
apiKey: process.env.ABLO_API_KEY,
logger,
});
The logger receives lifecycle, sync, retry, and rollback events. Avoid logging
request bodies that may contain customer data.
Public Imports
Only these imports are public SemVer surface:
@abloatai/ablo
@abloatai/ablo/schema
@abloatai/ablo/react
@abloatai/ablo/testing
dataSource(...) is exported from the root package for customer-owned storage
adapters. Everything outside the four import paths is internal to Ablo-owned
apps and infrastructure.