@abloatai/ablo 0.47.0 → 0.49.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 +227 -8
- package/README.md +23 -8
- package/docs/agent-messaging.md +2 -2
- package/docs/api.md +2 -2
- package/docs/cli.md +9 -1
- package/docs/concurrency-convention.md +82 -268
- package/docs/coordination.md +175 -906
- package/docs/data-sources.md +96 -0
- package/docs/guarantees.md +25 -21
- package/docs/how-it-works.md +10 -40
- package/docs/identity.md +48 -0
- package/docs/index.md +2 -2
- package/docs/migration.md +46 -443
- package/docs/operating-on-your-database.md +29 -36
- package/examples/README.md +24 -0
- package/examples/agent-turn.ts +37 -0
- package/examples/data-source/ablo-driver.ts +4 -4
- package/examples/data-source/customer-server.ts +9 -4
- package/examples/data-source/schema.ts +1 -1
- package/examples/expensive-agent-turn.ts +76 -0
- package/examples/quickstart.ts +9 -7
- package/package.json +7 -4
- package/docs/internal/README.md +0 -18
- package/docs/internal/agent-fleet-coordination-design.md +0 -171
- package/docs/internal/agent-orchestration.md +0 -57
- package/docs/internal/commit-identifiers.md +0 -91
- package/docs/internal/concurrency-open-decisions.md +0 -37
- package/docs/internal/data-source-reverse-channel.md +0 -147
- package/docs/internal/per-field-conflict-detection.md +0 -165
- package/docs/internal/postgres-replication.md +0 -64
- package/docs/internal/serializable-schema.md +0 -119
- package/docs/internal/structure.md +0 -32
package/docs/migration.md
CHANGED
|
@@ -1,461 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Upgrade Guide
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A safe workflow for upgrading the pre-1.0 SDK.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
change when you upgrade.
|
|
5
|
+
Ablo is pre-1.0, so a minor release may contain a breaking API change. Patch
|
|
6
|
+
releases remain compatible within the same minor line.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
> versions (`0.x.y`) never do. Pin a minor and read this page before bumping it.
|
|
8
|
+
## Upgrade safely
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
1. Pin the version you run instead of depending on a floating range.
|
|
11
|
+
2. Read the changelog entries between your installed and target versions.
|
|
12
|
+
3. Use the documentation bundled with the target package while changing code.
|
|
13
|
+
4. Run type-checks and tests before updating a production branch.
|
|
14
|
+
5. Push schema changes deliberately after reviewing the generated diff.
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| **0.35.0** | `causedByTaskId` write option + seven `turn_*` error codes removed | Delete the `causedByTaskId` argument from writes; a branch on `turn_validation_failed` was unreachable and can go with it |
|
|
20
|
-
| **0.34.0** | Presence verb renamed `watch` → `join` | `ablo.<model>.watch(ids)` → `ablo.<model>.join(ids)`; `useWatch` → `useJoin`; the `WatchOptions` / `UseWatchOptions` / `UseWatchReturn` types → `JoinOptions` / `UseJoinOptions` / `UseJoinReturn`; error code `model_watch_not_configured` → `model_join_not_configured` |
|
|
21
|
-
| **0.28.0** | Removed React placeholders that had no working runtime | `usePresence` → `usePeers` or `useJoin`; `useClaim` → `ablo.<model>.claim`; `SyncGroupProvider` / `useSyncGroup` → `useJoin({ scope })` |
|
|
22
|
-
| **0.11.0** | Historical `intent` → `claim` rename | The hook renamed in that release was later removed in 0.28.0. Current code uses `ablo.<model>.claim` or `useJoin` |
|
|
23
|
-
| **0.10.0** | Environment enum renamed `test`/`live` → `sandbox`/`production` | Historical: update code that branches on the environment (`'test'`→`'sandbox'`, `'live'`→`'production'`). That release retained `sk_test_`/`sk_live_`; current releases mint mode-free, branch-bound keys |
|
|
24
|
-
| **0.9.2** | `turn` primitive + agent-work `tasks` resource removed | Coordinate with `claim`; mint a scoped session instead of `agent().run()` |
|
|
25
|
-
| **0.9.2** | `intents` deprecated in favor of `claim` | Use `ablo.<model>.claim`; `ablo.intents` is now `@internal` |
|
|
26
|
-
| **0.9.0** | One options object per verb | `update(id, data, opts)` → `update({ id, data, ...opts })` |
|
|
27
|
-
| **0.9.0** | `claim` returns a disposable handle | `await using claim = await ablo.x.claim({ id })` |
|
|
28
|
-
| **0.8.0** | Flat coordination methods removed | `ablo.x.claimState(id)` → `ablo.x.claim.state({ id })` |
|
|
29
|
-
| **0.7.0** | Legacy React hooks removed | `useQuery`/`useOne`/`useMutate`/`useReader` → `useAblo()` + `ablo.<model>.*` |
|
|
30
|
-
| **0.6.0** | `subscribe` → `onChange`; `Resource` → `Model` rename | Rename listeners and `ablo.resource()` → `ablo.model()` |
|
|
31
|
-
| **0.5.0** | Intent-handle method renames | `acquire`→`claim`, `acquireOrAwait`→`claimOrWait`, … |
|
|
32
|
-
| **0.3.0** | `<SyncProvider>` / `createAbloContext()` / `withSync` removed | Use the umbrella `<AbloProvider>` |
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Next: key spelling follows capability, branch binding follows authority
|
|
37
|
-
|
|
38
|
-
New runtime credentials have one prefix per capability class:
|
|
39
|
-
|
|
40
|
-
```diff
|
|
41
|
-
- sk_live_… / sk_test_…
|
|
42
|
-
+ sk_…
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The same applies to `rk_`, `pk_`, and `ek_`. This does **not** merge branch
|
|
46
|
-
data or weaken production safety. Every key row is still immutably bound to one
|
|
47
|
-
project and one branch, and the server-confirmed `branchRoot` flag distinguishes
|
|
48
|
-
the protected production root from a development child.
|
|
49
|
-
|
|
50
|
-
Remove code that chooses an endpoint, environment, or confirmation policy by
|
|
51
|
-
matching `_live_` or `_test_`. Use the resolved identity (`ablo whoami` in the
|
|
52
|
-
CLI) instead. Existing suffixed credentials remain valid compatibility inputs,
|
|
53
|
-
so rollout does not require an immediate rotation.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 0.36.0: one lease is spelled `ttl`
|
|
58
|
-
|
|
59
|
-
```diff
|
|
60
|
-
- useJoin({ scope: { documents: [id] }, ttlSeconds: '5m' })
|
|
61
|
-
+ useJoin({ scope: { documents: [id] }, ttl: '5m' })
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`ablo.<model>.join(ids, { ttl })` has always said `ttl`, and so does every other
|
|
65
|
-
lease in the SDK: `claim`'s `ttl`, `ClaimLeaseOptions.ttl`. The lower-level join
|
|
66
|
-
surfaces said `ttlSeconds` while accepting exactly the same values, including
|
|
67
|
-
duration strings, so a `ttl: '5m'` handed down from the model verb arrived as
|
|
68
|
-
`ttlSeconds: '5m'`, a field asserting a unit its value did not carry.
|
|
69
|
-
|
|
70
|
-
Both spellings work until 0.37.0, and `ttl` wins if you pass both. The wire is
|
|
71
|
-
unchanged: it has always carried seconds and still does.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 0.35.0: the synchronous reads move under `local`
|
|
76
|
-
|
|
77
|
-
```diff
|
|
78
|
-
- const task = ablo.tasks.get(id);
|
|
79
|
-
- const open = ablo.tasks.getAll({ where: { status: 'open' } });
|
|
80
|
-
- const count = ablo.tasks.getCount({ where: { status: 'open' } });
|
|
81
|
-
+ const task = ablo.tasks.local.get(id);
|
|
82
|
-
+ const open = ablo.tasks.local.list({ where: { status: 'open' } });
|
|
83
|
-
+ const count = ablo.tasks.local.count({ where: { status: 'open' } });
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Options, return types, and reactivity inside `useAblo` selectors are unchanged.
|
|
87
|
-
Every verb now matches its asynchronous sibling, and `local` narrows the read to
|
|
88
|
-
what has already synced — which is what lets it return a value rather than a
|
|
89
|
-
promise.
|
|
90
|
-
|
|
91
|
-
`getAll` and `getCount` are distinctive enough to rename by search. `get` is not:
|
|
92
|
-
in most codebases it is outnumbered many times over by `Map.get` and
|
|
93
|
-
`headers.get`, and no search separates them. Upgrade the package first and let
|
|
94
|
-
the compiler name the sites — each one is a type error at exactly the call that
|
|
95
|
-
has to move.
|
|
96
|
-
|
|
97
|
-
## 0.35.0: `causedByTaskId` and the `turn_*` error codes removed
|
|
98
|
-
|
|
99
|
-
0.9.2 retired the `turn` primitive but left one field standing: `causedByTaskId`
|
|
100
|
-
on the write options bag. It was never usable. The server validated it against a
|
|
101
|
-
task record that nothing in the system has ever created, so supplying it had the
|
|
102
|
-
whole batch rejected with `turn_validation_failed`, while leaving it null passed
|
|
103
|
-
straight through. The safe way to use the option was to not use it.
|
|
104
|
-
|
|
105
|
-
**Removed:** `MutationOptions.causedByTaskId`, the seven `turn_*` error codes
|
|
106
|
-
(`turn_validation_failed`, `turn_open_failed`, `turn_close_failed`,
|
|
107
|
-
`turn_not_found`, `turn_foreign_agent`, `parent_turn_not_found`,
|
|
108
|
-
`parent_turn_foreign_agent`), and the stored row's provenance slice
|
|
109
|
-
(`deltaProvenanceSchema` and the `DeltaProvenance` type). `syncDeltaRowSchema` is
|
|
110
|
-
now the core and attribution slices composed.
|
|
111
|
-
|
|
112
|
-
```diff
|
|
113
|
-
- await ablo.documents.update({ id, data, causedByTaskId: turnId });
|
|
114
|
-
+ await ablo.documents.update({ id, data });
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Attribution is unaffected. A delta still records the actor, the `onBehalfOf`
|
|
118
|
-
principal behind a delegated write, the capability that authorized it, and the
|
|
119
|
-
claim it was made under — which is what answers "who did this, and by what
|
|
120
|
-
right." On the wire the field was optional and nullable, so a client that still
|
|
121
|
-
sends it is accepted and ignored.
|
|
122
|
-
|
|
123
|
-
The `caused_by_task_id` column stays, for the reason 0.9.2 gave when it kept it:
|
|
124
|
-
the audit hash-chain signs its value into every row. Dropping it is a versioned
|
|
125
|
-
migration of its own.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## 0.34.0: presence verb renamed `watch` → `join`
|
|
130
|
-
|
|
131
|
-
The model-level presence verb read like a data subscription but delivered
|
|
132
|
-
presence — who else is on a row and what they hold — so it now says what it
|
|
133
|
-
does. `ablo.<model>.join(ids, { ttl })` opens the participant handle
|
|
134
|
-
(`.peers`, `.claims`, `await using` disposal); the returned `status` was
|
|
135
|
-
already `'joined'`, and the layer beneath always called itself `join`, so the
|
|
136
|
-
verb now matches. `onChange` remains the way to hear row *values* change, and
|
|
137
|
-
`track` remains the durable premise for actors.
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
// before
|
|
141
|
-
await using room = await ablo.documents.watch(documentIds, { ttl: '5m' });
|
|
142
|
-
// after
|
|
143
|
-
await using room = await ablo.documents.join(documentIds, { ttl: '5m' });
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
The React hook follows: `useWatch({ scope })` → `useJoin({ scope })`. There is
|
|
147
|
-
no compatibility alias — rename the call sites and the `WatchOptions` /
|
|
148
|
-
`UseWatchOptions` / `UseWatchReturn` type imports.
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## 0.28.0: dead React multiplayer placeholders removed
|
|
153
|
-
|
|
154
|
-
Four React exports looked usable but had no live implementation:
|
|
155
|
-
|
|
156
|
-
- `usePresence` returned no provider-backed presence value. Use `usePeers` for
|
|
157
|
-
read-only presence or `useJoin` to join a scoped participant.
|
|
158
|
-
- `useClaim` depended on a callback the provider never supplied and always
|
|
159
|
-
threw. Use `ablo.<model>.claim({ id, ... })` for row claims or
|
|
160
|
-
`useJoin({ scope, claim: true })` for a scoped participant claim.
|
|
161
|
-
- `SyncGroupProvider` and `useSyncGroup` had no repository consumers. Pass the
|
|
162
|
-
scope directly to `useJoin({ scope })`.
|
|
163
|
-
|
|
164
|
-
There is no compatibility alias: the replacement APIs were already the only
|
|
165
|
-
working paths.
|
|
166
|
-
|
|
167
|
-
## 0.11.0: `intent` → `claim` rename completed
|
|
168
|
-
|
|
169
|
-
> **Historical note:** this section documents the 0.11.0 transition.
|
|
170
|
-
> `useClaim` was subsequently removed in 0.28.0 because its provider callback
|
|
171
|
-
> was never wired. Do not migrate new code to it; use the replacements above.
|
|
172
|
-
|
|
173
|
-
The coordination primitive has been `claim` since 0.9.2, but a few `intent`-named
|
|
174
|
-
surfaces lingered. 0.11.0 finishes the rename. There are three edits, all
|
|
175
|
-
mechanical:
|
|
176
|
-
|
|
177
|
-
**1. React hook.** `useIntent` is now `useClaim` (same signature):
|
|
178
|
-
|
|
179
|
-
```diff
|
|
180
|
-
- import { useIntent } from '@abloatai/ablo/react';
|
|
181
|
-
- const claimEditLayer = useIntent('editLayer');
|
|
182
|
-
+ import { useClaim } from '@abloatai/ablo/react';
|
|
183
|
-
+ const claimEditLayer = useClaim('editLayer');
|
|
16
|
+
```bash
|
|
17
|
+
npm install @abloatai/ablo@0.48
|
|
18
|
+
npx ablo docs
|
|
19
|
+
npx ablo docs api
|
|
20
|
+
npx ablo check
|
|
184
21
|
```
|
|
185
22
|
|
|
186
|
-
|
|
23
|
+
`npx ablo docs` is version-matched to the installed package. Prefer it during an
|
|
24
|
+
upgrade: the hosted website documents the newest release, which may expose a
|
|
25
|
+
method your pinned package does not yet contain.
|
|
187
26
|
|
|
188
|
-
|
|
189
|
-
declare module '@abloatai/ablo' {
|
|
190
|
-
interface Register {
|
|
191
|
-
- Intents: { editLayer: { slideId: string; layerId: string } };
|
|
192
|
-
+ Claims: { editLayer: { slideId: string; layerId: string } };
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**3. Type namespace.** The `Ablo.Intent.*` helper types moved to `Ablo.Claim.*`.
|
|
198
|
-
If you referenced them directly, rename the namespace; the shapes are unchanged.
|
|
199
|
-
|
|
200
|
-
> **Coordinated deploy required.** The on-the-wire frames moved from `intent_*`
|
|
201
|
-
> to `claim_*`. A `claim_*`-aware client cannot coordinate with an `intent_*`
|
|
202
|
-
> server (and vice-versa), so ship the client and server together. If you run a
|
|
203
|
-
> self-managed sync server, deploy it first.
|
|
204
|
-
|
|
205
|
-
Two non-breaking improvements ride along: claim-rejection errors now surface the
|
|
206
|
-
contending holders (`AbloClaimedError.claims` and a policy reason folded into the
|
|
207
|
-
message), and `participantKind` is the canonical `'user' | 'agent' | 'system'`
|
|
208
|
-
on presence and claim state.
|
|
209
|
-
|
|
210
|
-
## 0.10.0: environment enum `sandbox` / `production`; stateless HTTP transport
|
|
211
|
-
|
|
212
|
-
> Historical migration note: current development uses immutable branches, not a
|
|
213
|
-
> shared sandbox selector. The enum and compatibility fields below remain
|
|
214
|
-
> relevant to integrations written against 0.10, but new applications let the
|
|
215
|
-
> branch-bound credential select the plane.
|
|
216
|
-
|
|
217
|
-
### Environment enum rename (the only breaking change)
|
|
27
|
+
## What to review
|
|
218
28
|
|
|
219
|
-
|
|
220
|
-
**`sandbox`** (from `live` and `test`). At that release this was only a
|
|
221
|
-
vocabulary change at the type/API layer: on-the-wire keys still used
|
|
222
|
-
`sk_test_…` / `sk_live_…`. Current releases accept those legacy spellings but
|
|
223
|
-
mint mode-free `sk_…` keys whose persisted branch binding is authoritative.
|
|
29
|
+
Pay particular attention when a release changes:
|
|
224
30
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
31
|
+
- model method signatures or return values;
|
|
32
|
+
- claim acquisition, contention, or release behavior;
|
|
33
|
+
- credential scope or session minting;
|
|
34
|
+
- schema serialization and push validation;
|
|
35
|
+
- database connection or Data Source setup;
|
|
36
|
+
- error codes your application handles explicitly.
|
|
228
37
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
- const db = mode === 'test' ? testDb : liveDb;
|
|
233
|
-
+ const db = mode === 'sandbox' ? sandboxDb : productionDb;
|
|
234
|
-
// …
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
`commit` now also forwards `projectId`, `accountScope`, and `environment` to
|
|
240
|
-
source resolvers, so per-project and per-environment traffic can be routed to
|
|
241
|
-
distinct stores.
|
|
242
|
-
|
|
243
|
-
> **CLI note:** current login stores one mode-free `mk_` project management
|
|
244
|
-
> credential. Legacy runtime key slots remain readable during the cutover, but
|
|
245
|
-
> re-run `ablo login` before using branch/project commands so the CLI can store
|
|
246
|
-
> the new credential.
|
|
247
|
-
|
|
248
|
-
### New (non-breaking): `transport: 'http'`
|
|
249
|
-
|
|
250
|
-
`Ablo({ transport: 'http' })` returns a stateless `AbloHttpClient` for
|
|
251
|
-
server-side actors (agents, workers, serverless): the same `ablo.<model>` surface
|
|
252
|
-
and `claim` coordination, but each call is one HTTP round-trip with identity on
|
|
253
|
-
the Bearer credential — no websocket, no local synced pool. The return type
|
|
254
|
-
narrows, so stateful-only APIs (the `local` reads, `onChange`) become compile
|
|
255
|
-
errors instead of latent runtime gaps. Existing code keeps the default
|
|
256
|
-
`'websocket'` transport, unchanged.
|
|
257
|
-
|
|
258
|
-
```ts
|
|
259
|
-
const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: 'http' });
|
|
260
|
-
await ablo.tasks.update({ id, data: { status: 'done' } });
|
|
261
|
-
```
|
|
38
|
+
The release changelog names required edits next to the feature that changed.
|
|
39
|
+
Avoid branching on credential prefixes or undocumented internals; use exported
|
|
40
|
+
types and server-confirmed identity instead.
|
|
262
41
|
|
|
263
|
-
|
|
264
|
-
> the `transport: 'http'` surface. Keep a default-transport `server` client for
|
|
265
|
-
> minting short-lived credentials (see the 0.9.2 example below), and use the
|
|
266
|
-
> http client for the per-request reads and writes.
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
## 0.9.2: `turn` / agent-`tasks` removed; `intents` deprecated
|
|
271
|
-
|
|
272
|
-
The SDK's coordination surface is now exactly two things: `ablo.<model>` writes
|
|
273
|
-
and `claim`. The parallel `turn` / agent-`tasks` mechanism was redundant —
|
|
274
|
-
`claim` already serializes writers **and** carries the causal link (its `intent`
|
|
275
|
-
id rides on every guarded write), and the server stamps `actor` / `onBehalfOf` /
|
|
276
|
-
`capabilityId` onto every delta from the auth context.
|
|
277
|
-
|
|
278
|
-
**Removed:** `engine.beginTurn()`, the `Turn` handle and `Ablo.Turn` type,
|
|
279
|
-
`AbloApi.beginTurn`, `CommitCreateOptions.causedByTaskId`, the `agent().run()`
|
|
280
|
-
helper, and the agent/task type family (`Agent`, `AgentOptions`,
|
|
281
|
-
`AgentRunResult`, `Task`, `TaskResource`, …).
|
|
282
|
-
|
|
283
|
-
> **Note:** `ablo.tasks` is — and always was — the schema `tasks` **model**
|
|
284
|
-
> proxy. Only the agent-work *resource* of the same name was removed. If you have
|
|
285
|
-
> a `tasks` model in your schema, it is unaffected.
|
|
286
|
-
|
|
287
|
-
```diff
|
|
288
|
-
- const turn = await engine.beginTurn();
|
|
289
|
-
- await Ablo({ apiKey }).agent(agentId, opts).run(prompt, handler);
|
|
290
|
-
+ // Mint a scoped credential from a stateful (default-transport) server client —
|
|
291
|
-
+ // sessions.create lives on the stateful client, not on transport: 'http'.
|
|
292
|
-
+ const server = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
293
|
-
+ const { token } = await server.sessions.create({ agent: { id: agentId } });
|
|
294
|
-
+ const agent = Ablo({ schema, apiKey: token });
|
|
295
|
-
+ await using claim = await agent.tasks.claim({ id });
|
|
296
|
-
+ await agent.tasks.update({ id, data: { status: 'done' } });
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
Per-run token/cost now lives in Langfuse, not an `agent_tasks` table. The only
|
|
300
|
-
capability the client loses is the audit pane's "show everything this exact
|
|
301
|
-
prompt produced" filter (it keyed off `caused_by_task_id`); new writes leave that
|
|
302
|
-
column `null`. The server-side `agent_tasks` table, the `caused_by_task_id` delta
|
|
303
|
-
column, and the `agent_actions_log` hash-chain are intentionally **kept but
|
|
304
|
-
dormant** — they are load-bearing for the tamper-evident audit chain. The dead
|
|
305
|
-
`/v1/tasks` and `/api/agent/turn` route handlers were removed.
|
|
306
|
-
|
|
307
|
-
### `intents` → `claim`
|
|
308
|
-
|
|
309
|
-
```diff
|
|
310
|
-
- const lock = ablo.intents.editing(target);
|
|
311
|
-
+ await using claim = await ablo.documents.claim({ id });
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
`ablo.intents` still exists but is marked `@internal`. Use `ablo.<model>.claim`
|
|
315
|
-
everywhere you coordinate concurrent work.
|
|
316
|
-
|
|
317
|
-
---
|
|
318
|
-
|
|
319
|
-
## 0.9.0: one options object per verb; disposable `claim`
|
|
320
|
-
|
|
321
|
-
Every model verb takes a single options object, so the id, the data, and every
|
|
322
|
-
modifier are named siblings. Reactive local reads stay on the synchronous
|
|
323
|
-
`get(id)`.
|
|
324
|
-
|
|
325
|
-
```diff
|
|
326
|
-
- await ablo.tasks.update(id, { status: 'done' }, { wait: 'confirmed' })
|
|
327
|
-
+ await ablo.tasks.update({ id, data: { status: 'done' } })
|
|
328
|
-
|
|
329
|
-
- await ablo.tasks.retrieve(id)
|
|
330
|
-
+ await ablo.tasks.get({ id })
|
|
331
|
-
|
|
332
|
-
- useAblo((ablo) => ablo.tasks.retrieve(id)) ?? serverTask
|
|
333
|
-
+ useAblo((ablo) => ablo.tasks.local.get(id)) ?? serverTask
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
`claim` now returns a disposable handle instead of taking a callback. The handle
|
|
337
|
-
exposes the fresh row on `.data` and releases on scope exit.
|
|
338
|
-
|
|
339
|
-
```diff
|
|
340
|
-
- await ablo.tasks.claim(id, async (task) => {
|
|
341
|
-
- await ablo.tasks.update(task.id, { status: 'in_review' })
|
|
342
|
-
- })
|
|
343
|
-
+ await using claim = await ablo.tasks.claim({ id })
|
|
344
|
-
+ const task = claim.data
|
|
345
|
-
+ await ablo.tasks.update({ id: task.id, data: { status: 'in_review' } })
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
`claim.state`, `claim.queue`, `claim.release`, and `claim.reorder` also take the
|
|
349
|
-
options object.
|
|
350
|
-
|
|
351
|
-
---
|
|
352
|
-
|
|
353
|
-
## 0.8.0: callable `claim` namespace
|
|
354
|
-
|
|
355
|
-
The flat coordination methods are gone; everything lives under `claim`.
|
|
356
|
-
|
|
357
|
-
```diff
|
|
358
|
-
- await ablo.task.claimState(id)
|
|
359
|
-
- await ablo.task.release(id)
|
|
360
|
-
+ await ablo.task.claim.state(id)
|
|
361
|
-
+ await ablo.task.claim.release(id)
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
---
|
|
365
|
-
|
|
366
|
-
## 0.7.0: legacy React hooks removed
|
|
367
|
-
|
|
368
|
-
The query/mutation hooks were replaced by the single `useAblo()` accessor over
|
|
369
|
-
typed model methods.
|
|
370
|
-
|
|
371
|
-
```diff
|
|
372
|
-
- const { data } = useQuery('task', { where: { done: false } })
|
|
373
|
-
+ const ablo = useAblo()
|
|
374
|
-
+ const tasks = ablo.task.list({ where: { done: false } })
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
Removed: `useQuery`, `useOne`, `useMutate`, `useReader`. The `MutateActions`,
|
|
378
|
-
`ReaderActions`, and `ReaderFindOptions` types are still exported for callers
|
|
379
|
-
that reference them. This release also replaced the `{ error, reason }` error
|
|
380
|
-
shape with the canonical `{ type, code, message, doc_url, request_id }` envelope.
|
|
381
|
-
|
|
382
|
-
> **Note:** This — not 0.9.x — is the release where `useAblo()` became the one
|
|
383
|
-
> React read path. If you are coming from a 0.6.x or earlier app, this is your
|
|
384
|
-
> biggest hook migration.
|
|
385
|
-
|
|
386
|
-
---
|
|
387
|
-
|
|
388
|
-
## 0.6.0: `onChange` and the Resource → Model rename
|
|
389
|
-
|
|
390
|
-
```diff
|
|
391
|
-
- ablo.tasks.subscribe(cb)
|
|
392
|
-
+ ablo.tasks.onChange(cb)
|
|
393
|
-
|
|
394
|
-
- ablo.resource('tasks')
|
|
395
|
-
+ ablo.model('tasks')
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
Also renamed: `Ablo.Resource.*` → `Ablo.Model.*`, `ModelTarget.resource` →
|
|
399
|
-
`ModelTarget.model`, and error code `resource_not_found` → `model_not_found`.
|
|
400
|
-
(`subscribe` is reserved for an upcoming scope-grant verb.)
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
## 0.5.0: intent-handle method renames
|
|
405
|
-
|
|
406
|
-
On the model intent handle (`ablo.<model>.intent(id)`):
|
|
407
|
-
|
|
408
|
-
```diff
|
|
409
|
-
- handle.acquire() + handle.claim()
|
|
410
|
-
- handle.acquireOrAwait() + handle.claimOrWait()
|
|
411
|
-
- handle.settled() + handle.whenFree()
|
|
412
|
-
- handle.release() + handle.finish()
|
|
413
|
-
- handle.revoke() + handle.cancel()
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
The lower-level `IntentHandle` / `IntentLeaseHandle` (`ablo.intents.*`) were
|
|
417
|
-
unchanged at this release. (They were later folded under `claim` in 0.9.2.)
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## 0.3.0: umbrella `<AbloProvider>`
|
|
422
|
-
|
|
423
|
-
One provider component now owns the full React lifecycle. `<SyncProvider>`,
|
|
424
|
-
`createAbloContext()`, and `withSync` were removed.
|
|
425
|
-
|
|
426
|
-
```diff
|
|
427
|
-
- const { AbloProvider, useAblo } = createAbloContext<typeof schema>();
|
|
428
|
-
- <SyncProvider store={sync._store} organizationId={orgId}>
|
|
429
|
-
- <AbloProvider ablo={ablo}>{children}</AbloProvider>
|
|
430
|
-
- </SyncProvider>
|
|
431
|
-
+ const ablo = Ablo({ schema, apiKey });
|
|
432
|
-
+ <AbloProvider client={ablo}>
|
|
433
|
-
+ {children}
|
|
434
|
-
+ </AbloProvider>
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
`useSyncStatus()` changed from six booleans to a tagged union:
|
|
438
|
-
|
|
439
|
-
```diff
|
|
440
|
-
- const { isReady } = useSyncStatus()
|
|
441
|
-
+ const isReady = useSyncStatus().name === 'connected'
|
|
442
|
-
```
|
|
42
|
+
## Schema and database safety
|
|
443
43
|
|
|
444
|
-
|
|
44
|
+
An SDK upgrade and a database migration are separate operations.
|
|
445
45
|
|
|
446
|
-
|
|
46
|
+
- `ablo push` updates Ablo's typed schema contract. It does not run application
|
|
47
|
+
DDL or drop your tables.
|
|
48
|
+
- `ablo check` compares the contract with the connected database without
|
|
49
|
+
changing it.
|
|
50
|
+
- Your ORM or migration tool remains responsible for tables, columns,
|
|
51
|
+
constraints, and application data migrations.
|
|
447
52
|
|
|
448
|
-
|
|
53
|
+
When both need to change, deploy the database migration in a backwards-compatible
|
|
54
|
+
form first, push the compatible Ablo schema, then remove old application paths.
|
|
449
55
|
|
|
450
|
-
|
|
451
|
-
old code or old docs, this is the through-line:
|
|
56
|
+
## If an upgrade fails
|
|
452
57
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
| 0.8.0 | Callable `claim` namespace (`claim(id)`, `claim.state`, `claim.queue`, …) |
|
|
458
|
-
| 0.9.0 | `claim` returns an `await using` disposable handle |
|
|
459
|
-
| 0.9.2 | `intents` deprecated and made `@internal`: **`claim` is the one coordination API** |
|
|
58
|
+
Use the typed error code and request ID rather than matching message text. The
|
|
59
|
+
[Errors](./errors.md) reference gives the recovery step for each public error.
|
|
60
|
+
If a failure only occurs on the new minor version, keep the previous pinned
|
|
61
|
+
version in production while reproducing it against an isolated branch.
|
|
460
62
|
|
|
461
|
-
|
|
63
|
+
See the release changelog for version-specific changes and
|
|
64
|
+
[Deployment](./deployment.md) for the production rollout sequence.
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# Operating on Your Database
|
|
2
2
|
|
|
3
|
-
> Which actions
|
|
3
|
+
> Which actions are read-only, which writes need a guard, and which changes belong to a human.
|
|
4
4
|
|
|
5
5
|
Ablo sits over your database as a coordination layer, not an owner. It reads
|
|
6
|
-
your Postgres replication stream and routes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
name. This page is how to tell them apart, so you can work on a real database
|
|
11
|
-
without guessing which move is the dangerous one.
|
|
6
|
+
your Postgres replication stream and routes model writes into your own tables.
|
|
7
|
+
The hosted service does not run application DDL, migrations, or drops. That
|
|
8
|
+
boundary keeps model operations attributable while your migration tool remains
|
|
9
|
+
responsible for the database shape.
|
|
12
10
|
|
|
13
11
|
The habit that makes it easy is to look before you act. One command shows you
|
|
14
12
|
the real shape of your database measured against your schema, and changes
|
|
@@ -32,26 +30,27 @@ radius of anything you do through the model API:
|
|
|
32
30
|
action, run with your admin credential — never Ablo's.
|
|
33
31
|
- It **never owns your rows.** Canonical data stays in your tables; Ablo hosts
|
|
34
32
|
only the transaction log and the coordination state.
|
|
35
|
-
- Every model write is **
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
- Every model write is **checked against active claims and recorded.** A plain
|
|
34
|
+
write is last-write-wins when no claim applies. A functional update, held
|
|
35
|
+
claim, or explicit `readAt` guard protects a write that depends on an earlier
|
|
36
|
+
value.
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
Ablo prevents a non-holder from writing through another participant's claim by
|
|
39
|
+
default. It does not infer that every plain update is a read-modify-write
|
|
40
|
+
operation; use the guarded forms when lost-update protection matters.
|
|
42
41
|
|
|
43
42
|
## Three kinds of action
|
|
44
43
|
|
|
45
44
|
Sort any action you're about to take into one of these, and the right move
|
|
46
45
|
follows.
|
|
47
46
|
|
|
48
|
-
**
|
|
47
|
+
**Normal application operations.**
|
|
49
48
|
Reads (`get`, `list`), `ablo check`, and `ablo pull` observe and
|
|
50
49
|
never change anything. Previews — `--show-sql`, `--dry-run` — print the exact
|
|
51
50
|
SQL a command would run without executing it. Model writes through
|
|
52
|
-
`ablo.<model>.create` / `update` are
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
`ablo.<model>.create` / `update` are authorized, checked against active claims,
|
|
52
|
+
optimistic locally, rolled back locally if the server rejects them, and
|
|
53
|
+
recorded. Review their data effects as you would any application write.
|
|
55
54
|
|
|
56
55
|
**Verify first — needs one look at the live database.**
|
|
57
56
|
Routing an existing table's writes through a model requires the model to match
|
|
@@ -61,28 +60,23 @@ line in the report rather than a surprise at commit time. Decide the model shape
|
|
|
61
60
|
from what `check` tells you, then proceed. Nothing here is risky — it just reads
|
|
62
61
|
better after you've seen the ground truth.
|
|
63
62
|
|
|
64
|
-
**Hand to a human —
|
|
63
|
+
**Hand to a human — database administration.**
|
|
65
64
|
Raw DDL on the live database — `ALTER TABLE … OWNER TO`, adding or dropping a
|
|
66
|
-
column, changing a constraint — changes the database itself
|
|
67
|
-
|
|
65
|
+
column, changing a constraint — changes the database itself, so it belongs to a
|
|
66
|
+
person with their hand on it. So does
|
|
68
67
|
a `connect` cutover run with its confirmation skipped (`--yes`): the prompt
|
|
69
68
|
exists because the step provisions real roles and reconciles publication on a
|
|
70
69
|
live database, and on a shared or production database that confirmation is the
|
|
71
|
-
human's to give. Removing a model from your schema
|
|
72
|
-
|
|
70
|
+
human's to give. Removing a model from your pushed schema also deserves review
|
|
71
|
+
because clients will lose that typed API surface.
|
|
73
72
|
|
|
74
73
|
## The one action that isn't what it looks like
|
|
75
74
|
|
|
76
|
-
Deleting a model from `ablo/schema.ts`
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
real table on the next push* — and that one is answered against the database,
|
|
82
|
-
not the codebase. Before removing a model that maps a live table, confirm the
|
|
83
|
-
table is gone or empty and that your push path is additive; otherwise keep the
|
|
84
|
-
model until the data is dealt with. A `load: 'lazy'` mapping is often present
|
|
85
|
-
precisely to hold a table in place, so treat its comment as load-bearing.
|
|
75
|
+
Deleting a model from `ablo/schema.ts` removes it from the API contract the next
|
|
76
|
+
time you push the schema. Existing clients can no longer access that model
|
|
77
|
+
through Ablo. It does **not** drop the underlying table or its rows; database DDL
|
|
78
|
+
remains your migration tool's responsibility. Review client usage first, then
|
|
79
|
+
remove the model and push the schema as a normal application change.
|
|
86
80
|
|
|
87
81
|
## The verification loop
|
|
88
82
|
|
|
@@ -98,10 +92,9 @@ read-only checks:
|
|
|
98
92
|
- Read the row and its claim state before you write — `get` / `list`, and
|
|
99
93
|
`ablo.<model>.claim.state({ id })` for who is already working on it.
|
|
100
94
|
|
|
101
|
-
The pattern underneath all of it is steady:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
one `ablo check` away from certain.
|
|
95
|
+
The pattern underneath all of it is steady: observe first, use guarded writes
|
|
96
|
+
when a change depends on prior state, and review DDL and cutovers separately
|
|
97
|
+
because they change the database itself.
|
|
105
98
|
|
|
106
99
|
## See also
|
|
107
100
|
|
package/examples/README.md
CHANGED
|
@@ -26,6 +26,28 @@ Then:
|
|
|
26
26
|
- write with `ablo.weatherReports.update`
|
|
27
27
|
- dispose the client when the worker finishes
|
|
28
28
|
|
|
29
|
+
For read-reason-write work, pass the exact returned rows that informed the
|
|
30
|
+
decision. Their watermarks stay opaque:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const task = await ablo.tasks.get({ id: taskId });
|
|
34
|
+
const policy = await ablo.policies.get({ id: policyId });
|
|
35
|
+
const result = await model({ task, policy });
|
|
36
|
+
await ablo.tasks.update({
|
|
37
|
+
id: task.id,
|
|
38
|
+
data: result,
|
|
39
|
+
reads: [task, policy],
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This means: apply the update only if the rows used to produce it have not
|
|
44
|
+
changed. Incidental reads do nothing, and cloned or fabricated rows are
|
|
45
|
+
rejected locally.
|
|
46
|
+
|
|
47
|
+
`agent-turn.ts` is the cheap read/write path. `expensive-agent-turn.ts` adds a
|
|
48
|
+
heartbeating claim, post-grant model input, durable commit inspection, automatic
|
|
49
|
+
release, and a released-claim fencing check.
|
|
50
|
+
|
|
29
51
|
Import the same schema in every runtime. Use `commits.create` only when several
|
|
30
52
|
typed row operations must land atomically; ordinary writes stay on
|
|
31
53
|
`ablo.<model>.create/update/delete`.
|
|
@@ -39,6 +61,8 @@ root and a bare `quickstart.ts` won't be found.
|
|
|
39
61
|
```bash
|
|
40
62
|
cd packages/ablo
|
|
41
63
|
ABLO_API_KEY=sk_... npx tsx examples/quickstart.ts
|
|
64
|
+
ABLO_API_KEY=sk_... TASK_ID=task_... npx tsx examples/agent-turn.ts
|
|
65
|
+
ABLO_API_KEY=sk_... JOB_ID=job_... npx tsx examples/expensive-agent-turn.ts
|
|
42
66
|
```
|
|
43
67
|
|
|
44
68
|
## Data Source (customer-owned database)
|