@abloatai/ablo 0.7.0 → 0.9.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/CHANGELOG.md +72 -1
- package/README.md +80 -66
- package/dist/BaseSyncedStore.d.ts +73 -0
- package/dist/BaseSyncedStore.js +179 -5
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/SyncEngineContext.d.ts +2 -1
- package/dist/SyncEngineContext.js +5 -3
- package/dist/agent/session.js +6 -5
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +26 -36
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +259 -33
- package/dist/client/Ablo.js +276 -73
- package/dist/client/ApiClient.d.ts +52 -4
- package/dist/client/ApiClient.js +236 -66
- package/dist/client/auth.d.ts +21 -2
- package/dist/client/auth.js +77 -5
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +187 -79
- package/dist/client/createModelProxy.js +203 -68
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +63 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +19 -0
- package/dist/client/registerDataSource.js +59 -0
- package/dist/client/validateAbloOptions.d.ts +2 -1
- package/dist/client/validateAbloOptions.js +8 -7
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +92 -1
- package/dist/errorCodes.js +139 -7
- package/dist/errors.d.ts +54 -3
- package/dist/errors.js +192 -44
- package/dist/index.d.ts +23 -10
- package/dist/index.js +21 -8
- package/dist/keys/index.d.ts +76 -0
- package/dist/keys/index.js +171 -0
- package/dist/mutators/UndoManager.d.ts +86 -50
- package/dist/mutators/UndoManager.js +129 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +22 -14
- package/dist/react/AbloProvider.d.ts +23 -101
- package/dist/react/AbloProvider.js +61 -103
- package/dist/react/ClientSideSuspense.d.ts +1 -1
- package/dist/react/DefaultFallback.d.ts +1 -1
- package/dist/react/SyncGroupProvider.d.ts +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +3 -2
- package/dist/react/useAblo.d.ts +4 -4
- package/dist/react/useAblo.js +10 -5
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/react/useReactive.js +16 -3
- package/dist/schema/ddl.d.ts +26 -3
- package/dist/schema/ddl.js +152 -4
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.js +12 -0
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +7 -3
- package/dist/schema/serialize.js +6 -2
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +59 -0
- package/dist/source/adapter.js +19 -0
- package/dist/source/adapters/drizzle.d.ts +34 -0
- package/dist/source/adapters/drizzle.js +147 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +199 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +143 -0
- package/dist/source/contract.js +98 -0
- package/dist/source/index.d.ts +61 -10
- package/dist/source/index.js +98 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +56 -42
- package/dist/sync/ConnectionManager.d.ts +57 -1
- package/dist/sync/ConnectionManager.js +186 -11
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +241 -41
- package/dist/sync/NetworkProbe.d.ts +60 -18
- package/dist/sync/NetworkProbe.js +121 -23
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +113 -89
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/sync/participants.js +5 -2
- package/dist/transactions/TransactionQueue.js +13 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api-keys.md +5 -5
- package/docs/api.md +125 -65
- package/docs/audit.md +16 -9
- package/docs/cli.md +57 -47
- package/docs/client-behavior.md +54 -40
- package/docs/coordination.md +66 -80
- package/docs/data-sources.md +56 -34
- package/docs/examples/agent-human.md +74 -28
- package/docs/examples/ai-sdk-tool.md +29 -22
- package/docs/examples/existing-python-backend.md +41 -26
- package/docs/examples/nextjs.md +32 -17
- package/docs/examples/scoped-agent.md +43 -28
- package/docs/examples/server-agent.md +40 -15
- package/docs/guarantees.md +38 -27
- package/docs/identity.md +65 -59
- package/docs/index.md +30 -19
- package/docs/integration-guide.md +78 -78
- package/docs/interaction-model.md +43 -35
- package/docs/mcp/claude-code.md +11 -19
- package/docs/mcp/cursor.md +7 -25
- package/docs/mcp/windsurf.md +7 -20
- package/docs/mcp.md +103 -26
- package/docs/quickstart.md +63 -61
- package/docs/react.md +24 -16
- package/docs/roadmap.md +13 -13
- package/docs/schema-contract.md +111 -0
- package/docs/the-loop.md +21 -0
- package/examples/README.md +8 -4
- package/examples/data-source/README.md +10 -7
- package/examples/data-source/customer-server.ts +27 -25
- package/examples/data-source/run.ts +4 -3
- package/examples/quickstart.ts +1 -1
- package/llms.txt +55 -21
- package/package.json +48 -3
package/docs/audit.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Audit log
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
The audit log records who changed what in your org, and when — including
|
|
4
|
+
changes an AI agent made on a person's behalf. Every change is one row, and the
|
|
5
|
+
rows are signed in a chain so you can later prove the history wasn't altered.
|
|
6
|
+
You can filter it, page through it, and export it.
|
|
7
|
+
|
|
8
|
+
Every commit becomes one row.
|
|
5
9
|
|
|
6
10
|
## Row shape
|
|
7
11
|
|
|
@@ -12,10 +16,10 @@ tamper-evident, queryable, exportable.
|
|
|
12
16
|
actorId: string,
|
|
13
17
|
onBehalfOfKind: 'user' | 'agent' | 'system' | null,
|
|
14
18
|
onBehalfOfId: string | null,
|
|
15
|
-
credentialId: string | null,
|
|
16
|
-
credentialLabel: string | null,
|
|
19
|
+
credentialId: string | null, // the API key/credential used for the write
|
|
20
|
+
credentialLabel: string | null, // its human-readable name, for scanning the log
|
|
17
21
|
delegationChainRoot: string | null, // always points at a human
|
|
18
|
-
causedByRunId: string | null,
|
|
22
|
+
causedByRunId: string | null, // the agent run that produced this write — group every change from one run
|
|
19
23
|
actionType: string, // e.g. 'weatherReport.update'
|
|
20
24
|
modelName: string | null, // e.g. 'claude-opus-4-7'
|
|
21
25
|
diffSummary: unknown,
|
|
@@ -28,9 +32,9 @@ tamper-evident, queryable, exportable.
|
|
|
28
32
|
|
|
29
33
|
## Delegation chain
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
Every action traces back to a human. Even when an agent makes the change,
|
|
36
|
+
`delegationChainRoot` names the person who set that work in motion — there is no
|
|
37
|
+
audit row whose root is an agent.
|
|
34
38
|
|
|
35
39
|
## Verify
|
|
36
40
|
|
|
@@ -71,7 +75,10 @@ curl 'https://<your-app>/api/orgs/<slug>/audit/export?actorKind=agent&since=2026
|
|
|
71
75
|
> may-agent-writes.csv
|
|
72
76
|
```
|
|
73
77
|
|
|
74
|
-
CSV up to a hard cap
|
|
78
|
+
One request exports CSV up to a hard row cap. If your window is larger than the
|
|
79
|
+
cap, the response is truncated at the cap rather than erroring — so for large
|
|
80
|
+
windows, split the window by date and request each slice, or page through the
|
|
81
|
+
JSON `GET` endpoint above using `nextCursor`.
|
|
75
82
|
|
|
76
83
|
## Compliance posture
|
|
77
84
|
|
package/docs/cli.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
The `ablo` CLI gets you from an empty project to live-syncing data: scaffold a
|
|
4
4
|
schema, authenticate, push the schema, and watch it sync. Your
|
|
5
|
-
`defineSchema(...)` is the single source of truth
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`defineSchema(...)` is the single source of truth: whether you run the CLI
|
|
6
|
+
locally or push to the hosted server, the same engine turns it into the same
|
|
7
|
+
SQL — so what you test is what ships.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx ablo init # scaffold ablo/schema.ts + client
|
|
@@ -12,6 +12,14 @@ npx ablo login # authorize in the browser
|
|
|
12
12
|
npx ablo dev # push schema to the test sandbox + watch
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
**Two setup styles, and they pick your commands.** If your app database is the
|
|
16
|
+
source of truth, expose a [Data Source endpoint](./data-sources.md) and keep DB
|
|
17
|
+
credentials in your app. If you explicitly want Ablo to open a Postgres
|
|
18
|
+
connection, use the **Direct Postgres connector** commands: `ablo migrate`
|
|
19
|
+
applies changes to your own `DATABASE_URL`, and `ablo check` / `ablo pull`
|
|
20
|
+
adopt tables you already have. Hosted sandbox commands are tagged **Hosted**;
|
|
21
|
+
direct-connector commands are tagged **Direct Postgres**.
|
|
22
|
+
|
|
15
23
|
## Authenticate
|
|
16
24
|
|
|
17
25
|
`ablo login` runs the OAuth 2.0 device flow: it opens your browser, you choose
|
|
@@ -19,12 +27,12 @@ npx ablo dev # push schema to the test sandbox + watch
|
|
|
19
27
|
**test + live key pair** (90-day, restricted) and stores them locally. This
|
|
20
28
|
mirrors `stripe login`.
|
|
21
29
|
|
|
22
|
-
| Command
|
|
23
|
-
|
|
|
24
|
-
| `ablo login`
|
|
25
|
-
| `ablo logout`
|
|
26
|
-
| `ablo status`
|
|
27
|
-
| `ablo mode [test\|live]` | Switch the active mode. With no argument, prompts.
|
|
30
|
+
| Command | What it does |
|
|
31
|
+
| ------------------------ | -------------------------------------------------------------------------- |
|
|
32
|
+
| `ablo login` | Authorize in the browser; provisions + stores a test and a live key. |
|
|
33
|
+
| `ablo logout` | Remove the stored keys. |
|
|
34
|
+
| `ablo status` | Show the active org, mode, both keys (prefix + expiry), and server health. |
|
|
35
|
+
| `ablo mode [test\|live]` | Switch the active mode. With no argument, prompts. |
|
|
28
36
|
|
|
29
37
|
Keys are stored in `~/.config/ablo/config.json` (mode `0600`). In **CI**, don't
|
|
30
38
|
log in — set `ABLO_API_KEY`, which always overrides the stored key.
|
|
@@ -41,17 +49,18 @@ either mode) defines the same models test and live see; only the rows differ.
|
|
|
41
49
|
|
|
42
50
|
## Commands
|
|
43
51
|
|
|
44
|
-
| Command
|
|
45
|
-
|
|
|
46
|
-
| `ablo init`
|
|
47
|
-
| `ablo login` / `logout` / `status` | Authentication & status (above).
|
|
48
|
-
| `ablo mode [test\|live]`
|
|
49
|
-
| `ablo dev`
|
|
50
|
-
| `ablo logs`
|
|
51
|
-
| `ablo
|
|
52
|
-
| `ablo
|
|
53
|
-
| `ablo
|
|
54
|
-
| `ablo
|
|
52
|
+
| Command | What it does | Flags |
|
|
53
|
+
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
54
|
+
| `ablo init` | Scaffold `ablo/` (`schema.ts`, client, optional Data Source / agent / component), write `.env`, install the SDK. Offers to log in at the end. | — |
|
|
55
|
+
| `ablo login` / `logout` / `status` | Authentication & status (above). | — |
|
|
56
|
+
| `ablo mode [test\|live]` | Switch active mode. | — |
|
|
57
|
+
| `ablo dev` | **Hosted** — push the schema to your test sandbox, then watch `ablo/schema.ts` and re-push on save. | `--no-watch`, `--schema <path>`, `--export <name>`, `--url <url>` |
|
|
58
|
+
| `ablo logs` | Tail your scope's commit activity (`stripe logs tail`). Follows by default. | `-n, --tail <N>`, `--since <dur\|ts>`, `--model`, `--op`, `--json`, `--no-follow`, `--mode test\|live` |
|
|
59
|
+
| `ablo push` | **Hosted** — upload the schema to Ablo; the server diffs, migrates, and activates it. | `--force`, `--rename old:new`, `--backfill model.field=value`, `--schema`, `--export`, `--url` |
|
|
60
|
+
| `ablo migrate` | **Direct Postgres** — apply the schema to your own `DATABASE_URL` (you run the DDL). | `--dry-run`, `--output <file>`, `--schema`, `--export` |
|
|
61
|
+
| `ablo pull` | **Direct Postgres** — generate `defineSchema(...)` from your existing tables (read-only, like `prisma db pull`). | `--out <path>`, `--app-schema <name>`, `--import <pkg>`, `--force` |
|
|
62
|
+
| `ablo check` | **Direct Postgres** — verify your _existing_ tables fit the schema (read-only, no DDL). | `--schema <path>`, `--export <name>`, `--app-schema <name>` |
|
|
63
|
+
| `ablo generate` | Emit TypeScript types from the schema. | `--out <path>`, `--schema`, `--export` |
|
|
55
64
|
|
|
56
65
|
## `ablo dev`
|
|
57
66
|
|
|
@@ -98,10 +107,11 @@ point: review it, then run `ablo check`.
|
|
|
98
107
|
|
|
99
108
|
## `ablo check`
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
`ablo check` is how you adopt a database you already own. Instead of creating or
|
|
111
|
+
altering tables, it inspects your existing ones and tells you which fit the
|
|
112
|
+
schema: it introspects `DATABASE_URL`, compares each table to your
|
|
113
|
+
`defineSchema(...)`, and reports — per model — whether the table is adoptable.
|
|
114
|
+
It never writes or alters anything.
|
|
105
115
|
|
|
106
116
|
A table is adoptable when it has a primary key `id` and (for org-scoped models)
|
|
107
117
|
an `organization_id` column — the tenancy marker the engine isolates on. Every
|
|
@@ -131,12 +141,12 @@ If a table can't carry `organization_id` (or has business logic Ablo shouldn't
|
|
|
131
141
|
bypass), keep it behind a [Data Source endpoint](/data-sources) rather than
|
|
132
142
|
reshaping it. `ablo check` is read-only; it never proposes a migration.
|
|
133
143
|
|
|
134
|
-
## `migrate` vs `
|
|
144
|
+
## `migrate` (Direct Postgres) vs `push` (Hosted)
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
connecting clients
|
|
146
|
+
Same engine, two setups. If you use the **Direct Postgres connector**, use
|
|
147
|
+
`ablo migrate` — it applies the schema to your own `DATABASE_URL`, and you run
|
|
148
|
+
the DDL. If Ablo manages the sandbox/hosted store, use `ablo push` and
|
|
149
|
+
`ablo dev` — the server applies the change and version-gates connecting clients.
|
|
140
150
|
|
|
141
151
|
```bash
|
|
142
152
|
ablo migrate --dry-run # preview the exact SQL
|
|
@@ -148,20 +158,20 @@ ablo migrate --output schema.sql # write SQL to a file
|
|
|
148
158
|
|
|
149
159
|
The one type map, shared by both paths (there is no second mapping):
|
|
150
160
|
|
|
151
|
-
| Zod
|
|
152
|
-
|
|
|
153
|
-
| `z.string()`
|
|
154
|
-
| `z.number()`
|
|
155
|
-
| `z.boolean()`
|
|
156
|
-
| `z.date()`
|
|
157
|
-
| `z.enum([...])`
|
|
158
|
-
| `z.object` / `z.array` / `z.record` / `z.union` / `z.custom` | `JSONB`
|
|
159
|
-
| `.optional()` / `.nullable()`
|
|
161
|
+
| Zod | Postgres |
|
|
162
|
+
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
163
|
+
| `z.string()` | `TEXT` |
|
|
164
|
+
| `z.number()` | `DOUBLE PRECISION` — never `INTEGER`; a Zod number may be fractional, and truncating is silent data loss |
|
|
165
|
+
| `z.boolean()` | `BOOLEAN` |
|
|
166
|
+
| `z.date()` | `TIMESTAMPTZ` |
|
|
167
|
+
| `z.enum([...])` | `TEXT` + a `CHECK (col IN (...))` constraint |
|
|
168
|
+
| `z.object` / `z.array` / `z.record` / `z.union` / `z.custom` | `JSONB` |
|
|
169
|
+
| `.optional()` / `.nullable()` | nullable column |
|
|
160
170
|
|
|
161
171
|
Each table also gets the platform columns (`id`, `organization_id`,
|
|
162
172
|
`created_by`, `created_at`, `updated_at`), an `organization_id` index, and
|
|
163
|
-
row-level security
|
|
164
|
-
|
|
173
|
+
row-level security so each org only sees its own rows — the engine sets this per
|
|
174
|
+
request (via `current_setting('app.current_org_id')`); you don't manage it.
|
|
165
175
|
|
|
166
176
|
`.default(...)` is **not** emitted as a SQL column default — Zod applies the
|
|
167
177
|
default at write time (`create`), in one place, so a DB default and a schema
|
|
@@ -185,7 +195,7 @@ that broke and the Postgres SQLSTATE, not just "migration failed".
|
|
|
185
195
|
}
|
|
186
196
|
```
|
|
187
197
|
|
|
188
|
-
`ablo
|
|
198
|
+
`ablo push` (hosted) returns the canonical error envelope (HTTP 500),
|
|
189
199
|
which the SDK reconstructs as a typed `AbloServerError`:
|
|
190
200
|
|
|
191
201
|
```json
|
|
@@ -204,9 +214,9 @@ migration can't leave clients gated against tables that don't match.
|
|
|
204
214
|
|
|
205
215
|
## Environment
|
|
206
216
|
|
|
207
|
-
| Variable
|
|
208
|
-
|
|
|
209
|
-
| `ABLO_API_KEY`
|
|
210
|
-
| `ABLO_API_URL`
|
|
211
|
-
| `ABLO_AUTH_URL`
|
|
212
|
-
| `ABLO_CONFIG_DIR` / `XDG_CONFIG_HOME` | Where the credential file lives.
|
|
217
|
+
| Variable | Purpose | Default |
|
|
218
|
+
| ------------------------------------- | ------------------------------------------------------------------------ | -------------------------- |
|
|
219
|
+
| `ABLO_API_KEY` | Authenticate without `ablo login` (CI). Always overrides the stored key. | — |
|
|
220
|
+
| `ABLO_API_URL` | Control-plane / API host (`push`, `dev`, `status`). | `https://api.abloatai.com` |
|
|
221
|
+
| `ABLO_AUTH_URL` | Dashboard origin for `ablo login`'s device flow. | `https://abloatai.com` |
|
|
222
|
+
| `ABLO_CONFIG_DIR` / `XDG_CONFIG_HOME` | Where the credential file lives. | `~/.config/ablo` |
|
package/docs/client-behavior.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Client Behavior
|
|
2
2
|
|
|
3
|
-
This page
|
|
3
|
+
When several writers touch the same data at once — a person in the browser, a Server Action, an agent worker — the SDK decides whose write lands and how the others find out. This page is the reference for that: per-write options like `wait` and `onStale`, claiming a record so your slow work runs uninterrupted, and which errors are safe to retry.
|
|
4
|
+
|
|
5
|
+
Claims don't lock. If another writer holds the row, `claim` waits for them, re-reads the fresh row, then hands it to you — so two writers serialize instead of clobbering.
|
|
4
6
|
|
|
5
7
|
## Constructor
|
|
6
8
|
|
|
@@ -45,42 +47,48 @@ Each schema model becomes a typed model:
|
|
|
45
47
|
```ts
|
|
46
48
|
await ablo.ready();
|
|
47
49
|
|
|
48
|
-
const
|
|
49
|
-
const local = ablo.weatherReports.
|
|
50
|
+
const report = await ablo.weatherReports.retrieve({ id: 'report_stockholm' });
|
|
51
|
+
const local = ablo.weatherReports.get('report_stockholm');
|
|
50
52
|
|
|
51
|
-
await ablo.weatherReports.create({ location: 'Stockholm', status: 'pending' });
|
|
52
|
-
await ablo.weatherReports.update('report_stockholm', { status: 'ready' },
|
|
53
|
-
await ablo.weatherReports.delete('report_stockholm',
|
|
53
|
+
await ablo.weatherReports.create({ data: { location: 'Stockholm', status: 'pending' } });
|
|
54
|
+
await ablo.weatherReports.update({ id: 'report_stockholm', data: { status: 'ready' }, wait: 'confirmed' });
|
|
55
|
+
await ablo.weatherReports.delete({ id: 'report_stockholm', wait: 'confirmed' });
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
`
|
|
57
|
-
`
|
|
58
|
+
Call `retrieve`/`list` first — they fetch from the server and you `await` them.
|
|
59
|
+
After that, `get`/`getAll`/`getCount` read the already-synced data instantly with
|
|
60
|
+
no `await`, and stay reactive in render. Use the async pair to load, the sync trio
|
|
61
|
+
to read.
|
|
58
62
|
|
|
59
|
-
`
|
|
60
|
-
`where`, `filter`, `orderBy`, `limit`, `offset`, and `
|
|
61
|
-
`'live'`; pass `'archived'` or `'all'` when you
|
|
62
|
-
rows.
|
|
63
|
+
`getAll` accepts the same practical read options the React selector path uses:
|
|
64
|
+
`where`, `filter`, `orderBy`, `limit`, `offset`, and `state`. The `state`
|
|
65
|
+
lifecycle filter defaults to `'live'`; pass `'archived'` or `'all'` when you
|
|
66
|
+
intentionally want non-live rows.
|
|
63
67
|
|
|
64
68
|
## Multiplayer Behavior
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
Two writers both try to mark `report_stockholm` ready at the same time. To stop
|
|
71
|
+
the second write from silently overwriting the first, every participant goes
|
|
72
|
+
through the same model client path. A human Server Action, a browser view, and an
|
|
73
|
+
agent worker can all use `ablo.weatherReports`:
|
|
69
74
|
|
|
70
75
|
```ts
|
|
71
|
-
const
|
|
76
|
+
const report = await ablo.weatherReports.retrieve({ id });
|
|
72
77
|
const snap = ablo.snapshot({ weatherReports: id });
|
|
73
78
|
|
|
74
|
-
await ablo.weatherReports.update(
|
|
79
|
+
await ablo.weatherReports.update({
|
|
80
|
+
id,
|
|
81
|
+
data: patch,
|
|
75
82
|
readAt: snap.stamp,
|
|
76
83
|
onStale: 'reject',
|
|
77
84
|
wait: 'confirmed',
|
|
78
85
|
});
|
|
79
86
|
```
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
Once the server accepts the write, every other connected client gets the new row
|
|
89
|
+
automatically — no polling or manual refresh on your side. React clients that use
|
|
90
|
+
`useAblo((ablo) => ablo.weatherReports.get(id))` receive the new row, and selectors
|
|
91
|
+
such as `useAblo((ablo) => ablo.weatherReports.claim.state({ id }))`
|
|
84
92
|
receive active claim state. There is
|
|
85
93
|
no extra multiplayer setup beyond routing shared state through Ablo.
|
|
86
94
|
|
|
@@ -90,17 +98,15 @@ until the app reports it through Data Source events.
|
|
|
90
98
|
## Per-Write Options
|
|
91
99
|
|
|
92
100
|
```ts
|
|
93
|
-
await ablo.weatherReports.update(
|
|
94
|
-
'report_stockholm',
|
|
95
|
-
{ status: 'ready' },
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
},
|
|
103
|
-
);
|
|
101
|
+
await ablo.weatherReports.update({
|
|
102
|
+
id: 'report_stockholm',
|
|
103
|
+
data: { status: 'ready' },
|
|
104
|
+
wait: 'confirmed',
|
|
105
|
+
readAt: snap.stamp,
|
|
106
|
+
onStale: 'reject',
|
|
107
|
+
idempotencyKey: 'report_stockholm:mark-ready:v1',
|
|
108
|
+
timeout: 20_000,
|
|
109
|
+
});
|
|
104
110
|
```
|
|
105
111
|
|
|
106
112
|
| Option | Purpose |
|
|
@@ -113,26 +119,34 @@ await ablo.weatherReports.update(
|
|
|
113
119
|
|
|
114
120
|
## Claimed Behavior
|
|
115
121
|
|
|
122
|
+
If your update involves a slow step — an API call, an LLM round-trip — and someone
|
|
123
|
+
else might write the same record meanwhile, claiming the record stops you from
|
|
124
|
+
overwriting their change. Check who holds the record with `claim.state({ id })`, then
|
|
125
|
+
take it with `claim({ id })`:
|
|
126
|
+
|
|
116
127
|
```ts
|
|
117
|
-
const active = ablo.weatherReports.
|
|
128
|
+
const active = ablo.weatherReports.claim.state({ id: 'report_stockholm' });
|
|
118
129
|
|
|
119
130
|
if (active) {
|
|
120
131
|
return { status: 'claimed', active };
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
await ablo.weatherReports.claim('report_stockholm'
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
const handle = await ablo.weatherReports.claim({ id: 'report_stockholm' });
|
|
135
|
+
await ablo.weatherReports.update({ id: handle.data.id, data: { status: 'ready' } });
|
|
136
|
+
await handle.release();
|
|
126
137
|
```
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
`claim.state({ id })` returns the current holder (or nothing) without ever blocking.
|
|
140
|
+
When you call `claim({ id })`, the SDK queues other claimers behind you, re-reads
|
|
141
|
+
the latest row, then hands you the fresh row — so you can't overwrite a change you didn't
|
|
142
|
+
see. Options on the claim:
|
|
130
143
|
|
|
131
|
-
- default `claim` waits in the fair queue and re-reads before
|
|
144
|
+
- default `claim` waits in the fair queue and re-reads before handing you the row;
|
|
132
145
|
- `{ wait: false }` rejects with `AbloClaimedError` instead of queuing;
|
|
133
146
|
- `{ maxQueueDepth }` rejects if the wait line is already too deep.
|
|
134
147
|
|
|
135
|
-
|
|
148
|
+
While waiting, schema clients learn when the claim clears from the live claim
|
|
149
|
+
stream, so they never poll.
|
|
136
150
|
|
|
137
151
|
## Errors
|
|
138
152
|
|
|
@@ -154,7 +168,7 @@ All SDK errors extend `AbloError` and carry a stable `type`.
|
|
|
154
168
|
import { AbloClaimedError } from '@abloatai/ablo';
|
|
155
169
|
|
|
156
170
|
try {
|
|
157
|
-
await ablo.weatherReports.update('report_stockholm', { status: 'ready' },
|
|
171
|
+
await ablo.weatherReports.update({ id: 'report_stockholm', data: { status: 'ready' }, wait: 'confirmed' });
|
|
158
172
|
} catch (error) {
|
|
159
173
|
if (error instanceof AbloClaimedError) {
|
|
160
174
|
return { status: 'claimed' };
|