@abloatai/ablo 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +29 -0
- package/README.md +3 -3
- package/dist/BaseSyncedStore.js +39 -32
- package/dist/batching/index.d.ts +57 -0
- package/dist/batching/index.js +150 -0
- package/dist/cli.cjs +158 -40
- package/dist/client/Ablo.d.ts +16 -25
- package/dist/client/Ablo.js +1 -1
- package/dist/client/auth.js +11 -0
- package/dist/client/createModelProxy.d.ts +33 -8
- package/dist/client/createModelProxy.js +4 -4
- package/dist/errorCodes.d.ts +3 -1
- package/dist/errorCodes.js +10 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +2 -2
- package/dist/schema/model.d.ts +38 -84
- package/dist/schema/model.js +12 -12
- package/dist/schema/roles.d.ts +49 -0
- package/dist/schema/roles.js +21 -0
- package/dist/schema/schema.d.ts +1 -1
- package/dist/schema/schema.js +1 -1
- package/dist/schema/serialize.d.ts +4 -2
- package/dist/schema/serialize.js +4 -2
- package/dist/schema/sugar.d.ts +7 -28
- package/dist/schema/sugar.js +2 -7
- package/dist/schema/sync-delta-row.d.ts +2 -0
- package/dist/schema/sync-delta-row.js +2 -1
- package/dist/schema/tenancy.d.ts +67 -28
- package/dist/schema/tenancy.js +93 -23
- package/dist/server/commit.d.ts +8 -3
- package/docs/api.md +7 -6
- package/docs/cli.md +43 -4
- package/docs/client-behavior.md +2 -2
- package/docs/coordination.md +12 -12
- package/docs/examples/agent-human.md +6 -6
- package/docs/examples/ai-sdk-tool.md +1 -1
- package/docs/examples/existing-python-backend.md +0 -2
- package/docs/examples/nextjs.md +2 -2
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/examples/server-agent.md +4 -4
- package/docs/identity.md +27 -20
- package/docs/index.md +0 -1
- package/docs/integration-guide.md +12 -9
- package/docs/interaction-model.md +1 -1
- package/docs/mcp.md +17 -5
- package/docs/quickstart.md +3 -3
- package/docs/react.md +69 -0
- package/llms.txt +2 -3
- package/package.json +8 -2
- package/docs/mcp/claude-code.md +0 -35
- package/docs/mcp/cursor.md +0 -35
- package/docs/mcp/windsurf.md +0 -33
- package/docs/roadmap.md +0 -55
- package/docs/the-loop.md +0 -21
- package/llms-full.txt +0 -396
package/llms-full.txt
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
# Ablo Full Context
|
|
2
|
-
|
|
3
|
-
Ablo is the state coordination layer for apps where humans and agents edit the same data.
|
|
4
|
-
|
|
5
|
-
Use AI SDK for the agent loop. Use Ablo when agent reads and writes must persist to the database, coordinate with concurrent human or agent work, and leave an audit trail.
|
|
6
|
-
|
|
7
|
-
The product surface should read like Stripe, Turbopuffer, or Reducto: one product-name import, models by dot access, optional subpaths only for schema, React, and tests.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import Ablo from '@abloatai/ablo';
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Public Surface
|
|
14
|
-
|
|
15
|
-
Public imports:
|
|
16
|
-
|
|
17
|
-
- `@abloatai/ablo` — `Ablo`, errors, typed model clients, claims, `dataSource`, and advanced schema-less protocol models.
|
|
18
|
-
- `@abloatai/ablo/schema` — `defineSchema`, `model`, `z`, relations, schema types.
|
|
19
|
-
- `@abloatai/ablo/react` — React provider and hooks.
|
|
20
|
-
- `@abloatai/ablo/testing` — test harnesses and mocks.
|
|
21
|
-
|
|
22
|
-
TYPES: the project registers its schema ONCE via declaration merging — `npx ablo init` scaffolds `ablo/register.ts` (a regular `.ts` module beside schema.ts, NOT a hand-authored `.d.ts`): `import type { schema } from './schema'; declare module '@abloatai/ablo' { interface Register { Schema: typeof schema } }`. The top-level `import type` makes `declare module` MERGE (augment) the SDK's Register interface rather than collide — same shape TanStack Router uses in src/router.tsx; any `.ts` file in tsconfig include works, never imported. Then model types are one parameter: `type Task = Model<'tasks'>` (import type { Model } from '@abloatai/ablo/schema'). Do NOT teach `InferModel` (deprecated) or the two-param `Model<typeof schema,'tasks'>` unless multiple schemas exist. Never hand-write model interfaces — derive from the schema. To NAME the client type (function param, context value), infer from the value: `type Sync = typeof sync` — same idiom as tRPC `typeof appRouter` / Drizzle `typeof db`; it resolves the typed overload at the call site. Do NOT use `ReturnType<typeof Ablo>` (collapses to the untyped last overload) and do NOT import a bespoke client-type generic — there is none.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Do not teach `/api`, `/agent`, `/ai-sdk`, `/core`, `/realtime`, or `internal/*` as public imports. The Data Source surface — `/source`, `/source/next`, `/source/drizzle`, `/source/kysely`, `/source/conformance` — IS public (it's how a customer-owned database is wired).
|
|
26
|
-
|
|
27
|
-
The canonical integration doc is `integration-guide`. It explains the end-to-end
|
|
28
|
-
path for schema, React selectors, your Data Source-backed database, multiplayer,
|
|
29
|
-
and agent workers. Use it before inventing a new setup
|
|
30
|
-
flow.
|
|
31
|
-
|
|
32
|
-
## Production Guarantees
|
|
33
|
-
|
|
34
|
-
`wait: 'confirmed'` resolves after the server accepts the write. Schema model
|
|
35
|
-
writes are optimistic; a server rejection rolls back local state and raises a
|
|
36
|
-
typed `AbloError`.
|
|
37
|
-
|
|
38
|
-
Use `snapshot(...)` and `readAt` when an agent write depends on state it already
|
|
39
|
-
read. `onStale: 'reject'` prevents lost updates by rejecting if the target
|
|
40
|
-
changed after the snapshot.
|
|
41
|
-
|
|
42
|
-
Claims are live coordination signals, not database locks. Reads never block on a
|
|
43
|
-
claim — to wait for a row to free up, `claim({ id })` it and the claim queues
|
|
44
|
-
fairly behind the current holder.
|
|
45
|
-
|
|
46
|
-
Agent run bookkeeping is internal. Most users should not create run ledger
|
|
47
|
-
records or scoped access credentials manually.
|
|
48
|
-
|
|
49
|
-
## Primary App Path
|
|
50
|
-
|
|
51
|
-
Declare models in a schema, then use typed model clients:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import Ablo from '@abloatai/ablo';
|
|
55
|
-
import { defineSchema, model, z } from '@abloatai/ablo/schema';
|
|
56
|
-
|
|
57
|
-
const schema = defineSchema({
|
|
58
|
-
weatherReports: model({
|
|
59
|
-
id: z.string(),
|
|
60
|
-
location: z.string(),
|
|
61
|
-
status: z.enum(['pending', 'ready']),
|
|
62
|
-
forecast: z.string().optional(),
|
|
63
|
-
updatedAt: z.string(),
|
|
64
|
-
}),
|
|
65
|
-
projects: model({
|
|
66
|
-
id: z.string(),
|
|
67
|
-
name: z.string(),
|
|
68
|
-
}),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
72
|
-
|
|
73
|
-
const report = await ablo.weatherReports.retrieve({ id: 'report_stockholm' });
|
|
74
|
-
if (!report) throw new Error('Row not found');
|
|
75
|
-
|
|
76
|
-
await using claim = await ablo.weatherReports.claim({ id: 'report_stockholm' });
|
|
77
|
-
const updated = await ablo.weatherReports.update({
|
|
78
|
-
id: claim.data.id,
|
|
79
|
-
data: { status: 'ready', forecast: await getForecast(claim.data) },
|
|
80
|
-
wait: 'confirmed',
|
|
81
|
-
});
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The normal app API (every verb takes ONE options object):
|
|
85
|
-
|
|
86
|
-
- `ablo.<model>.retrieve({ id })` — async read of one row from the server
|
|
87
|
-
- `ablo.<model>.list({ where })` — async read of many from the server
|
|
88
|
-
- `ablo.<model>.get(id)` / `getAll({ where })` / `getCount({ where })` — synchronous local-graph reads (reactive in React render)
|
|
89
|
-
- `ablo.<model>.create({ data, id? })`
|
|
90
|
-
- `ablo.<model>.update({ id, data })`
|
|
91
|
-
- `ablo.<model>.delete({ id })`
|
|
92
|
-
- `await using claim = await ablo.<model>.claim({ id })` — disposable claim handle; read the fresh row off `claim.data`; auto-releases on scope exit
|
|
93
|
-
- `ablo.<model>.claim.state({ id })` / `claim.queue({ id })` / `claim.release({ id })` / `claim.reorder({ id, order })`
|
|
94
|
-
|
|
95
|
-
The synchronous reads (`getAll`/`getCount`) accept `where`, `filter`, `orderBy`,
|
|
96
|
-
`limit`, `offset`, and `state`; state defaults to `'live'`, with `'archived'`
|
|
97
|
-
and `'all'` for lifecycle-aware reads.
|
|
98
|
-
|
|
99
|
-
Use `ablo.model<T>(name)` only when the caller intentionally does not have a schema, such as custom server agents, MCP routes, migration scripts, or generic admin tools.
|
|
100
|
-
|
|
101
|
-
React reads should use selector `useAblo`:
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
const report = useAblo((ablo) => ablo.weatherReports.get(id)) ?? serverReport;
|
|
105
|
-
const visibleReports = useAblo((ablo) =>
|
|
106
|
-
ablo.weatherReports.getAll({ where: { projectId } }),
|
|
107
|
-
);
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Use zero-argument `useAblo()` only for callbacks, effects, and writes that need
|
|
111
|
-
the provider-owned client:
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
const ablo = useAblo();
|
|
115
|
-
await ablo?.weatherReports.update({ id, data: patch, wait: 'confirmed' });
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Treat `useQuery`, `useOne`, `useReader`, and `useMutate` as compatibility hooks
|
|
119
|
-
for older string-keyed integrations. Do not teach them as the first React API.
|
|
120
|
-
|
|
121
|
-
## Multiplayer
|
|
122
|
-
|
|
123
|
-
Multiplayer is an effect of the normal model API, not a separate setup path.
|
|
124
|
-
When human UI, server actions, and agents use the same
|
|
125
|
-
`Ablo({ schema, apiKey })` client and write through `ablo.<model>`, Ablo
|
|
126
|
-
coordinates the shared model stream:
|
|
127
|
-
|
|
128
|
-
- confirmed model writes fan out as realtime deltas,
|
|
129
|
-
- React hooks read the latest row and active claims,
|
|
130
|
-
- claims announce active work before a commit,
|
|
131
|
-
- `snapshot(...)` plus `readAt` protects against stale writes.
|
|
132
|
-
|
|
133
|
-
Direct database writes outside Ablo are not coordinated until the app reports
|
|
134
|
-
them through Data Source events. In Data Source mode, writes made through Ablo
|
|
135
|
-
still coordinate normally because Ablo sends the signed commit request, receives
|
|
136
|
-
canonical rows, and fans out the resulting deltas.
|
|
137
|
-
|
|
138
|
-
For existing Python backends, keep the Python service layer and database as the
|
|
139
|
-
source of truth. Add one signed Data Source endpoint, keep initial page loads on
|
|
140
|
-
existing APIs if needed, use `useAblo((ablo) => ablo.<model>.get(id)) ?? serverRow`
|
|
141
|
-
for live rows, then migrate buttons one at a time from direct Python endpoint
|
|
142
|
-
writes to `ablo.<model>.update(...)`.
|
|
143
|
-
|
|
144
|
-
This applies to any API-backed app: Python, Rails, Go, or Node. The backend keeps
|
|
145
|
-
its service layer and DB credentials. Ablo gets a Data Source endpoint and uses
|
|
146
|
-
`ABLO_API_KEY`, not a database URL.
|
|
147
|
-
|
|
148
|
-
The connection-string path is the PRIMARY one: pass `databaseUrl` explicitly to
|
|
149
|
-
`Ablo({ databaseUrl })`. It is NOT auto-read from the environment — a
|
|
150
|
-
`DATABASE_URL` set for another tool (Prisma, Drizzle, docker-compose) is ignored
|
|
151
|
-
unless you pass `databaseUrl`. The typical user ALREADY has a Postgres (often
|
|
152
|
-
Prisma-managed for auth/audit/log tables that are NOT in the Ablo schema); Ablo
|
|
153
|
-
syncs a SUBSET of models against it. Most users do NOT run `ablo migrate` — they
|
|
154
|
-
ADOPT existing tables with `npx ablo pull` / `npx ablo check`, or keep managing
|
|
155
|
-
tables with their own migration tool. Run `npx ablo migrate` only when Ablo
|
|
156
|
-
should OWN the tables. NOTE: Ablo's CLOUD connects to your Postgres over the
|
|
157
|
-
NETWORK, so a localhost / private-range DB is unreachable and rejected — for a
|
|
158
|
-
local dev DB, expose a signed Data Source endpoint (your app proxies to it) or
|
|
159
|
-
use the hosted sandbox (no DB needed).
|
|
160
|
-
|
|
161
|
-
When the user DOES use the connection-string path: the role must be NON-superuser
|
|
162
|
-
and NON-BYPASSRLS — Ablo enforces row-level security and rejects owner roles with
|
|
163
|
-
`database_role_cannot_enforce_rls`. Neon's and Supabase's default dashboard
|
|
164
|
-
strings use the database OWNER (e.g. `neondb_owner`) and ARE rejected. EASIEST: have the user run `npx ablo migrate` — it detects the unsafe role and creates the scoped one automatically from their machine (owner credential never reaches Ablo; new DATABASE_URL written to the env file). Manual alternative — create a scoped role first (`CREATE ROLE ablo_app LOGIN PASSWORD '...'
|
|
165
|
-
NOSUPERUSER NOBYPASSRLS; GRANT CREATE, CONNECT ON DATABASE <db> TO ablo_app;
|
|
166
|
-
GRANT CREATE, USAGE ON SCHEMA public TO ablo_app;`), then swap user/password
|
|
167
|
-
into the same host/db string.
|
|
168
|
-
|
|
169
|
-
## Client Behavior
|
|
170
|
-
|
|
171
|
-
The options that matter: `schema` and `apiKey`. Everything else
|
|
172
|
-
(`persistence`, `logger`, transport tuning) has correct defaults — do NOT set
|
|
173
|
-
`baseURL`; the default already routes to the hosted API, and overriding it
|
|
174
|
-
breaks the connection. It exists only for self-hosted/proxy setups the human
|
|
175
|
-
explicitly asks for.
|
|
176
|
-
|
|
177
|
-
`databaseUrl` is an OPTIONAL, server-only option on `Ablo(...)`. It is NOT
|
|
178
|
-
auto-read from the environment — pass it EXPLICITLY to register your Postgres
|
|
179
|
-
directly (the primary connection-string path). Omit it when you expose a signed
|
|
180
|
-
Data Source endpoint (so customer-owned app databases stay private), or when
|
|
181
|
-
trying Ablo against the hosted sandbox (apiKey only, no database).
|
|
182
|
-
|
|
183
|
-
Important per-write options: `wait`, `readAt`, `onStale`,
|
|
184
|
-
`idempotencyKey`, and `timeout`.
|
|
185
|
-
|
|
186
|
-
Errors: `AbloAuthenticationError`, `AbloPermissionError`, `AbloRateLimitError`,
|
|
187
|
-
`AbloIdempotencyError`, `AbloConnectionError`, `AbloValidationError`,
|
|
188
|
-
`AbloServerError`, `AbloStaleContextError`, and `AbloClaimedError`.
|
|
189
|
-
|
|
190
|
-
Retry transport failures and 5xx with backoff only when the operation is
|
|
191
|
-
idempotent. Do not blindly retry validation, permission, idempotency, claimed, or
|
|
192
|
-
stale-context errors without changing the request.
|
|
193
|
-
|
|
194
|
-
## Sandboxes
|
|
195
|
-
|
|
196
|
-
There are two sandbox surfaces:
|
|
197
|
-
|
|
198
|
-
- Public `/sandbox` is a deterministic visual playground. It demonstrates shared
|
|
199
|
-
state, active claims, stale-write rejection, receipts, and deltas without
|
|
200
|
-
making real Ablo calls. It is agent-first: it should expose a prompt that can
|
|
201
|
-
be pasted into Claude Code or Codex to wire one real model through Ablo.
|
|
202
|
-
- Authenticated org sandboxes are real test environments. The default sandbox is
|
|
203
|
-
the Stripe-style sandbox for an org. It has an isolated sync group prefix,
|
|
204
|
-
can mint `sk_test_*` keys, and can be reset without touching live state. The
|
|
205
|
-
sandbox CAN host rows in Ablo's test plane, so you can try Ablo with NO
|
|
206
|
-
database — `apiKey` only, no `databaseUrl` — like Stripe test mode. (In
|
|
207
|
-
production, your own Postgres is the system of record.)
|
|
208
|
-
|
|
209
|
-
Additional org sandboxes can start blank or copy live configuration. Keep
|
|
210
|
-
customer production traffic on `sk_live_*`; use `sk_test_*` for app setup, Data
|
|
211
|
-
Source endpoint wiring, and agent integration testing.
|
|
212
|
-
|
|
213
|
-
The coding-agent handoff should ask for a narrow integration: one schema model,
|
|
214
|
-
one Ablo client module, one React selector read, one typed model write with
|
|
215
|
-
`readAt`/`onStale: 'reject'`/`wait: 'confirmed'`, and one smoke test where two
|
|
216
|
-
writers prove claim/stale behavior.
|
|
217
|
-
|
|
218
|
-
## Data Sources
|
|
219
|
-
|
|
220
|
-
Use these public environment names:
|
|
221
|
-
|
|
222
|
-
- `ABLO_API_KEY` — SDK authentication for app and agent code. Where it comes
|
|
223
|
-
from: the human runs `npx ablo login` once (browser; an agent must not run
|
|
224
|
-
it), and `npx ablo push` then writes `ABLO_API_KEY=sk_test_…` into
|
|
225
|
-
`.env.local` automatically (and gitignores it). Check the environment and
|
|
226
|
-
`.env.local` for PRESENCE only (`grep -cq '^ABLO_API_KEY=' .env.local`)
|
|
227
|
-
before asking the human for a key — never print or echo the key value; a
|
|
228
|
-
secret in agent output lives in the conversation history forever.
|
|
229
|
-
|
|
230
|
-
Do not ask customers to paste their app database URL into Ablo. If their app
|
|
231
|
-
database is canonical, they expose a Data Source endpoint and keep database
|
|
232
|
-
credentials inside their app.
|
|
233
|
-
|
|
234
|
-
Every schema model is backed by the customer's own database. The SDK methods
|
|
235
|
-
`ablo.<model>.create/update/delete` produce a signed commit request to the
|
|
236
|
-
customer's Data Source route, and that route writes the app database.
|
|
237
|
-
|
|
238
|
-
When the customer's database is canonical, expose one signed source route:
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
import { dataSourceNext } from '@abloatai/ablo/source/next';
|
|
242
|
-
import { prismaDataSource } from '@abloatai/ablo/source';
|
|
243
|
-
import { schema } from './ablo.schema';
|
|
244
|
-
import { prisma } from './lib/prisma';
|
|
245
|
-
|
|
246
|
-
// The adapter owns commit / idempotency / outbox — no hand-written commit.
|
|
247
|
-
export const { POST } = dataSourceNext({
|
|
248
|
-
schema,
|
|
249
|
-
apiKey: process.env.ABLO_API_KEY!,
|
|
250
|
-
adapter: prismaDataSource(prisma, schema), // or drizzleDataSource(db, schema) / kyselyDataSource(db, schema)
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
Commit request shape:
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
{
|
|
258
|
-
type: 'commit',
|
|
259
|
-
clientTxId: 'tx_...',
|
|
260
|
-
operations: [
|
|
261
|
-
{ type: 'UPDATE', model: 'weatherReports', id: 'report_stockholm', input, readAt, onStale: 'reject' },
|
|
262
|
-
],
|
|
263
|
-
scope: { participantId, participantKind, organizationId, requiredSyncGroups, mode: 'live' },
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Return `{ rows }` for normal apps or `{ deltas }` for sources that already
|
|
268
|
-
compute canonical change events. External writes come back through the source
|
|
269
|
-
`events` handler or `POST /api/source/events`.
|
|
270
|
-
|
|
271
|
-
## Advanced Schema Options
|
|
272
|
-
|
|
273
|
-
Teach schema as model fields and relations first:
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
const schema = defineSchema({
|
|
277
|
-
weatherReports: model({
|
|
278
|
-
id: z.string(),
|
|
279
|
-
location: z.string(),
|
|
280
|
-
}),
|
|
281
|
-
});
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
Advanced helpers such as `mutable`, `readOnly`, `field`, `indexed`, query definitions, `load` strategies, parent metadata, and sync-group formats are still part of the schema package for offline/cache/indexing-heavy apps. Do not put them in the first example unless the user asks for local persistence, offline sync, custom loading, or low-level indexing behavior.
|
|
285
|
-
|
|
286
|
-
## Server Agent Path
|
|
287
|
-
|
|
288
|
-
Server agents should import the same schema as the app and use the same model
|
|
289
|
-
methods. Claim the row around slow work, then write through `ablo.<model>`.
|
|
290
|
-
|
|
291
|
-
```ts
|
|
292
|
-
import Ablo from '@abloatai/ablo';
|
|
293
|
-
|
|
294
|
-
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
295
|
-
|
|
296
|
-
await using claim = await ablo.weatherReports.claim({ id: 'report_stockholm' });
|
|
297
|
-
const forecast = await getForecast(claim.data);
|
|
298
|
-
await ablo.weatherReports.update({
|
|
299
|
-
id: claim.data.id,
|
|
300
|
-
data: { status: 'ready', forecast },
|
|
301
|
-
wait: 'confirmed',
|
|
302
|
-
});
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Do not require users to manually create protocol bookkeeping objects in the
|
|
306
|
-
common agent path.
|
|
307
|
-
|
|
308
|
-
## Model
|
|
309
|
-
|
|
310
|
-
The read shape depends on whether the client has a local graph:
|
|
311
|
-
|
|
312
|
-
- The stateful `ablo.<model>.retrieve({ id })` returns the BARE row (`T | undefined`), and `list({ where })` returns `T[]`. Read coordination metadata (state watermark, active claims) separately via `ablo.snapshot(...)` and `ablo.<model>.claim.state({ id })`.
|
|
313
|
-
- The stateless HTTP / `.model(name)` `retrieve({ id })` returns a `ModelRead<T>` envelope because it has no local graph to carry the watermark; `list({ where })` still returns `T[]`.
|
|
314
|
-
|
|
315
|
-
```ts
|
|
316
|
-
type ModelRead<T> = {
|
|
317
|
-
data: T;
|
|
318
|
-
stamp: number;
|
|
319
|
-
claims: ModelClaim[];
|
|
320
|
-
};
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
`stamp` is the state watermark. Pass it to writes as `readAt` (from `ablo.snapshot(...)` on the stateful client, or from the envelope on the HTTP client).
|
|
324
|
-
|
|
325
|
-
`claims` lists active work on the target. Reads are allowed while another participant is working. The caller decides whether to return, fail, or wait.
|
|
326
|
-
|
|
327
|
-
## Claimed Behavior
|
|
328
|
-
|
|
329
|
-
Claimed behavior is explicit, and there are only two policies:
|
|
330
|
-
|
|
331
|
-
- `ifClaimed: 'return'` (the default) returns the row plus the current claims with the read.
|
|
332
|
-
- `ifClaimed: 'fail'` throws `AbloClaimedError`.
|
|
333
|
-
|
|
334
|
-
Reads never block on a claim. To wait for a row to free up, `claim({ id })` it —
|
|
335
|
-
the claim queues fairly behind the current holder and is granted when the row
|
|
336
|
-
frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
|
|
337
|
-
|
|
338
|
-
```ts
|
|
339
|
-
// Read a claimed row without blocking, surfacing who holds it:
|
|
340
|
-
await api.model('reports').retrieve({
|
|
341
|
-
id: 'report_stockholm',
|
|
342
|
-
ifClaimed: 'return',
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// Or wait for it to free up by queueing your own claim:
|
|
346
|
-
await api.model('reports').claim({ id: 'report_stockholm' });
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## Write
|
|
350
|
-
|
|
351
|
-
Use model methods as the normal write path:
|
|
352
|
-
|
|
353
|
-
```ts
|
|
354
|
-
const snap = ablo.snapshot({ weatherReports: 'report_stockholm' });
|
|
355
|
-
const updated = await ablo.weatherReports.update({
|
|
356
|
-
id: 'report_stockholm',
|
|
357
|
-
data: { status: 'ready' },
|
|
358
|
-
readAt: snap.stamp,
|
|
359
|
-
onStale: 'reject',
|
|
360
|
-
wait: 'confirmed',
|
|
361
|
-
});
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Write checks:
|
|
365
|
-
|
|
366
|
-
- authorization
|
|
367
|
-
- stale state via `readAt`
|
|
368
|
-
- active claim conflicts
|
|
369
|
-
- idempotency when an idempotency key is provided
|
|
370
|
-
|
|
371
|
-
Use `commits.create(...)` only for low-level batch writes.
|
|
372
|
-
|
|
373
|
-
## Receipt
|
|
374
|
-
|
|
375
|
-
Schema model writes return the updated row. Passing `wait: 'confirmed'` waits for
|
|
376
|
-
the server confirmation before resolving. Lower-level model writes return a
|
|
377
|
-
receipt object for custom runtimes that need protocol-level proof.
|
|
378
|
-
|
|
379
|
-
## Interaction Model
|
|
380
|
-
|
|
381
|
-
The common agent run is:
|
|
382
|
-
|
|
383
|
-
1. Read the row through `ablo.<model>.retrieve({ id })` (or `list({ where })`).
|
|
384
|
-
2. Claim it: `await using claim = await ablo.<model>.claim({ id })` — waits if held, hands you `claim.data`.
|
|
385
|
-
3. Do the slow work from `claim.data`.
|
|
386
|
-
4. Write with `ablo.<model>.update({ id, data })`.
|
|
387
|
-
5. The claim auto-releases when it goes out of scope (`await using`).
|
|
388
|
-
|
|
389
|
-
The underlying primitives are Model, Claim, Commit, and Receipt. Most schema
|
|
390
|
-
users should not call `commits.create(...)` directly.
|
|
391
|
-
|
|
392
|
-
## Public HTTP Routes
|
|
393
|
-
|
|
394
|
-
- `GET /v1/models/{model}/{id}` — read state plus active claims
|
|
395
|
-
- `POST /v1/commits` — apply mutations
|
|
396
|
-
Use `Authorization: Bearer <api key>` for platform calls.
|