@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.
Files changed (181) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.md +80 -66
  3. package/dist/BaseSyncedStore.d.ts +73 -0
  4. package/dist/BaseSyncedStore.js +179 -5
  5. package/dist/Model.d.ts +42 -0
  6. package/dist/Model.js +103 -44
  7. package/dist/SyncEngineContext.d.ts +2 -1
  8. package/dist/SyncEngineContext.js +5 -3
  9. package/dist/agent/session.js +6 -5
  10. package/dist/ai-sdk/coordination-context.js +4 -0
  11. package/dist/ai-sdk/index.d.ts +56 -47
  12. package/dist/ai-sdk/index.js +56 -47
  13. package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
  14. package/dist/ai-sdk/intent-broadcast.js +11 -4
  15. package/dist/ai-sdk/wrap.d.ts +14 -11
  16. package/dist/ai-sdk/wrap.js +11 -13
  17. package/dist/auth/credentialSource.d.ts +34 -0
  18. package/dist/auth/credentialSource.js +63 -0
  19. package/dist/auth/index.d.ts +2 -22
  20. package/dist/auth/index.js +26 -36
  21. package/dist/auth/schemas.d.ts +35 -0
  22. package/dist/auth/schemas.js +53 -0
  23. package/dist/client/Ablo.d.ts +259 -33
  24. package/dist/client/Ablo.js +276 -73
  25. package/dist/client/ApiClient.d.ts +52 -4
  26. package/dist/client/ApiClient.js +236 -66
  27. package/dist/client/auth.d.ts +21 -2
  28. package/dist/client/auth.js +77 -5
  29. package/dist/client/createInternalComponents.d.ts +2 -0
  30. package/dist/client/createInternalComponents.js +8 -1
  31. package/dist/client/createModelProxy.d.ts +187 -79
  32. package/dist/client/createModelProxy.js +203 -68
  33. package/dist/client/httpClient.d.ts +71 -0
  34. package/dist/client/httpClient.js +69 -0
  35. package/dist/client/identity.d.ts +2 -6
  36. package/dist/client/identity.js +63 -11
  37. package/dist/client/index.d.ts +1 -0
  38. package/dist/client/index.js +1 -0
  39. package/dist/client/registerDataSource.d.ts +19 -0
  40. package/dist/client/registerDataSource.js +59 -0
  41. package/dist/client/validateAbloOptions.d.ts +2 -1
  42. package/dist/client/validateAbloOptions.js +8 -7
  43. package/dist/core/DatabaseManager.js +30 -2
  44. package/dist/core/openIDBWithTimeout.d.ts +36 -0
  45. package/dist/core/openIDBWithTimeout.js +88 -1
  46. package/dist/errorCodes.d.ts +92 -1
  47. package/dist/errorCodes.js +139 -7
  48. package/dist/errors.d.ts +54 -3
  49. package/dist/errors.js +192 -44
  50. package/dist/index.d.ts +23 -10
  51. package/dist/index.js +21 -8
  52. package/dist/keys/index.d.ts +76 -0
  53. package/dist/keys/index.js +171 -0
  54. package/dist/mutators/UndoManager.d.ts +86 -50
  55. package/dist/mutators/UndoManager.js +129 -22
  56. package/dist/mutators/inverseOp.d.ts +129 -0
  57. package/dist/mutators/inverseOp.js +74 -0
  58. package/dist/mutators/readerActions.d.ts +1 -1
  59. package/dist/mutators/undoApply.d.ts +42 -0
  60. package/dist/mutators/undoApply.js +143 -0
  61. package/dist/query/client.d.ts +10 -9
  62. package/dist/query/client.js +22 -14
  63. package/dist/react/AbloProvider.d.ts +23 -101
  64. package/dist/react/AbloProvider.js +61 -103
  65. package/dist/react/ClientSideSuspense.d.ts +1 -1
  66. package/dist/react/DefaultFallback.d.ts +1 -1
  67. package/dist/react/SyncGroupProvider.d.ts +1 -1
  68. package/dist/react/index.d.ts +3 -2
  69. package/dist/react/index.js +3 -2
  70. package/dist/react/useAblo.d.ts +4 -4
  71. package/dist/react/useAblo.js +10 -5
  72. package/dist/react/useCurrentUserId.d.ts +1 -1
  73. package/dist/react/useCurrentUserId.js +1 -1
  74. package/dist/react/useMutators.js +19 -12
  75. package/dist/react/useReactive.js +16 -3
  76. package/dist/schema/ddl.d.ts +26 -3
  77. package/dist/schema/ddl.js +152 -4
  78. package/dist/schema/index.d.ts +4 -0
  79. package/dist/schema/index.js +12 -0
  80. package/dist/schema/model.d.ts +11 -0
  81. package/dist/schema/model.js +2 -0
  82. package/dist/schema/openapi.d.ts +28 -0
  83. package/dist/schema/openapi.js +118 -0
  84. package/dist/schema/plane.d.ts +23 -0
  85. package/dist/schema/plane.js +19 -0
  86. package/dist/schema/relation.d.ts +20 -0
  87. package/dist/schema/serialize.d.ts +7 -3
  88. package/dist/schema/serialize.js +6 -2
  89. package/dist/schema/sync-delta-row.d.ts +157 -0
  90. package/dist/schema/sync-delta-row.js +102 -0
  91. package/dist/schema/sync-delta-wire.d.ts +180 -0
  92. package/dist/schema/sync-delta-wire.js +102 -0
  93. package/dist/server/adapter.d.ts +156 -0
  94. package/dist/server/adapter.js +19 -0
  95. package/dist/server/commit.d.ts +82 -0
  96. package/dist/server/commit.js +1 -0
  97. package/dist/server/index.d.ts +14 -0
  98. package/dist/server/index.js +1 -0
  99. package/dist/server/next.d.ts +51 -0
  100. package/dist/server/next.js +47 -0
  101. package/dist/server/read-config.d.ts +60 -0
  102. package/dist/server/read-config.js +8 -0
  103. package/dist/server/storage-mode.d.ts +17 -0
  104. package/dist/server/storage-mode.js +12 -0
  105. package/dist/source/adapter.d.ts +59 -0
  106. package/dist/source/adapter.js +19 -0
  107. package/dist/source/adapters/drizzle.d.ts +34 -0
  108. package/dist/source/adapters/drizzle.js +147 -0
  109. package/dist/source/adapters/memory.d.ts +12 -0
  110. package/dist/source/adapters/memory.js +114 -0
  111. package/dist/source/adapters/prisma.d.ts +57 -0
  112. package/dist/source/adapters/prisma.js +199 -0
  113. package/dist/source/conformance.d.ts +32 -0
  114. package/dist/source/conformance.js +134 -0
  115. package/dist/source/contract.d.ts +143 -0
  116. package/dist/source/contract.js +98 -0
  117. package/dist/source/index.d.ts +61 -10
  118. package/dist/source/index.js +98 -0
  119. package/dist/source/next.d.ts +33 -0
  120. package/dist/source/next.js +26 -0
  121. package/dist/sync/BootstrapHelper.d.ts +10 -0
  122. package/dist/sync/BootstrapHelper.js +56 -42
  123. package/dist/sync/ConnectionManager.d.ts +57 -1
  124. package/dist/sync/ConnectionManager.js +186 -11
  125. package/dist/sync/HydrationCoordinator.d.ts +93 -17
  126. package/dist/sync/HydrationCoordinator.js +241 -41
  127. package/dist/sync/NetworkProbe.d.ts +60 -18
  128. package/dist/sync/NetworkProbe.js +121 -23
  129. package/dist/sync/SyncWebSocket.d.ts +45 -70
  130. package/dist/sync/SyncWebSocket.js +113 -89
  131. package/dist/sync/createIntentStream.js +10 -1
  132. package/dist/sync/participants.js +5 -2
  133. package/dist/transactions/TransactionQueue.js +13 -1
  134. package/dist/types/streams.d.ts +9 -0
  135. package/dist/utils/mobx-setup.js +1 -0
  136. package/dist/webhooks/events.d.ts +38 -0
  137. package/dist/webhooks/events.js +40 -0
  138. package/dist/webhooks/index.d.ts +10 -0
  139. package/dist/webhooks/index.js +10 -0
  140. package/dist/wire/errorEnvelope.d.ts +34 -0
  141. package/dist/wire/errorEnvelope.js +86 -0
  142. package/dist/wire/frames.d.ts +119 -0
  143. package/dist/wire/frames.js +1 -0
  144. package/dist/wire/index.d.ts +24 -0
  145. package/dist/wire/index.js +21 -0
  146. package/dist/wire/listEnvelope.d.ts +45 -0
  147. package/dist/wire/listEnvelope.js +17 -0
  148. package/docs/api-keys.md +5 -5
  149. package/docs/api.md +125 -65
  150. package/docs/audit.md +16 -9
  151. package/docs/cli.md +57 -47
  152. package/docs/client-behavior.md +54 -40
  153. package/docs/coordination.md +66 -80
  154. package/docs/data-sources.md +56 -34
  155. package/docs/examples/agent-human.md +74 -28
  156. package/docs/examples/ai-sdk-tool.md +29 -22
  157. package/docs/examples/existing-python-backend.md +41 -26
  158. package/docs/examples/nextjs.md +32 -17
  159. package/docs/examples/scoped-agent.md +43 -28
  160. package/docs/examples/server-agent.md +40 -15
  161. package/docs/guarantees.md +38 -27
  162. package/docs/identity.md +65 -59
  163. package/docs/index.md +30 -19
  164. package/docs/integration-guide.md +78 -78
  165. package/docs/interaction-model.md +43 -35
  166. package/docs/mcp/claude-code.md +11 -19
  167. package/docs/mcp/cursor.md +7 -25
  168. package/docs/mcp/windsurf.md +7 -20
  169. package/docs/mcp.md +103 -26
  170. package/docs/quickstart.md +63 -61
  171. package/docs/react.md +24 -16
  172. package/docs/roadmap.md +13 -13
  173. package/docs/schema-contract.md +111 -0
  174. package/docs/the-loop.md +21 -0
  175. package/examples/README.md +8 -4
  176. package/examples/data-source/README.md +10 -7
  177. package/examples/data-source/customer-server.ts +27 -25
  178. package/examples/data-source/run.ts +4 -3
  179. package/examples/quickstart.ts +1 -1
  180. package/llms.txt +55 -21
  181. package/package.json +48 -3
@@ -3,22 +3,12 @@
3
3
  ## Install
4
4
 
5
5
  ```bash
6
- claude mcp add --transport http ablo-sync https://<your-app>/api/mcp
6
+ claude mcp add --transport http ablo https://<your-app>/api/mcp
7
7
  ```
8
8
 
9
- That's it. The next `/help` in Claude Code will list the Ablo Sync tools.
10
-
11
- ## With auth
12
-
13
- If your deployment requires a scoped bearer token (production setups should):
14
-
15
- ```bash
16
- claude mcp add --transport http ablo-sync https://<your-app>/api/mcp \
17
- --header "Authorization=Bearer $ABLO_MCP_TOKEN"
18
- ```
19
-
20
- Create a session-scoped bearer token from your server or dashboard — see
21
- [MCP overview](/docs/mcp#auth).
9
+ That's it no token or header needed. The endpoint is public and serves
10
+ only docs, schema lint, and scaffolds. The next `/help` in Claude Code will
11
+ list the Ablo Sync tools.
22
12
 
23
13
  ## Verify
24
14
 
@@ -28,16 +18,18 @@ In Claude Code, run:
28
18
  /mcp list
29
19
  ```
30
20
 
31
- You should see `ablo-sync` with the model tools enumerated.
21
+ You should see `ablo` with the integration tools enumerated:
22
+ `search_ablo_docs`, `get_recipe`, `get_api_surface`, `validate_schema`,
23
+ `scaffold_app`.
32
24
 
33
25
  ## Removing
34
26
 
35
27
  ```bash
36
- claude mcp remove ablo-sync
28
+ claude mcp remove ablo
37
29
  ```
38
30
 
39
31
  ## More
40
32
 
41
- - [MCP overview](/docs/mcp) — how the transport works.
42
- - [Cursor setup](/docs/mcp/cursor) — same JSON, different UI.
43
- - [Windsurf setup](/docs/mcp/windsurf) — same JSON, different UI.
33
+ - [MCP overview](/docs/mcp) — what the server exposes and how the transport works.
34
+ - [Cursor setup](/docs/mcp/cursor) — same URL, different UI.
35
+ - [Windsurf setup](/docs/mcp/windsurf) — same URL, different UI.
@@ -7,7 +7,7 @@ Add the Ablo Sync MCP server to Cursor's `mcp.json`:
7
7
  ```json
8
8
  {
9
9
  "mcpServers": {
10
- "ablo-sync": {
10
+ "ablo": {
11
11
  "transport": "http",
12
12
  "url": "https://<your-app>/api/mcp"
13
13
  }
@@ -15,39 +15,21 @@ Add the Ablo Sync MCP server to Cursor's `mcp.json`:
15
15
  }
16
16
  ```
17
17
 
18
- The file lives at `~/.cursor/mcp.json` on macOS / Linux.
18
+ The file lives at `~/.cursor/mcp.json` on macOS / Linux. No auth header is
19
+ needed — the endpoint is public and serves only docs, schema lint, and
20
+ scaffolds.
19
21
 
20
22
  Restart Cursor. The Ablo Sync tools appear under the MCP icon in the agent
21
23
  panel.
22
24
 
23
- ## With auth
24
-
25
- Add a `headers` block:
26
-
27
- ```json
28
- {
29
- "mcpServers": {
30
- "ablo-sync": {
31
- "transport": "http",
32
- "url": "https://<your-app>/api/mcp",
33
- "headers": {
34
- "Authorization": "Bearer $ABLO_MCP_TOKEN"
35
- }
36
- }
37
- }
38
- }
39
- ```
40
-
41
- Cursor expands shell-style env vars in this block. Set `ABLO_MCP_TOKEN`
42
- in your shell config.
43
-
44
25
  ## Verify
45
26
 
46
27
  In Cursor's agent panel, open the MCP tools list. You should see the
47
- Ablo Sync model tools and their JSON schemas.
28
+ Ablo Sync integration tools and their JSON schemas: `search_ablo_docs`,
29
+ `get_recipe`, `get_api_surface`, `validate_schema`, `scaffold_app`.
48
30
 
49
31
  ## More
50
32
 
51
- - [MCP overview](/docs/mcp) — how the transport works.
33
+ - [MCP overview](/docs/mcp) — what the server exposes and how the transport works.
52
34
  - [Claude Code setup](/docs/mcp/claude-code) — CLI install.
53
35
  - [Windsurf setup](/docs/mcp/windsurf) — same JSON shape.
@@ -7,7 +7,7 @@ Add the Ablo Sync MCP server to Windsurf's MCP config:
7
7
  ```json
8
8
  {
9
9
  "mcpServers": {
10
- "ablo-sync": {
10
+ "ablo": {
11
11
  "transport": "http",
12
12
  "url": "https://<your-app>/api/mcp"
13
13
  }
@@ -16,31 +16,18 @@ Add the Ablo Sync MCP server to Windsurf's MCP config:
16
16
  ```
17
17
 
18
18
  The config path differs by platform — Windsurf surfaces it in Settings →
19
- Cascade → MCP. Restart Windsurf after saving.
20
-
21
- ## With auth
22
-
23
- ```json
24
- {
25
- "mcpServers": {
26
- "ablo-sync": {
27
- "transport": "http",
28
- "url": "https://<your-app>/api/mcp",
29
- "headers": {
30
- "Authorization": "Bearer $ABLO_MCP_TOKEN"
31
- }
32
- }
33
- }
34
- }
35
- ```
19
+ Cascade → MCP. Restart Windsurf after saving. No auth header is needed —
20
+ the endpoint is public and serves only docs, schema lint, and scaffolds.
36
21
 
37
22
  ## Verify
38
23
 
39
24
  Cascade's MCP panel lists every configured server with its tools. You
40
- should see `ablo-sync` with model tools enumerated.
25
+ should see `ablo` with the integration tools enumerated:
26
+ `search_ablo_docs`, `get_recipe`, `get_api_surface`, `validate_schema`,
27
+ `scaffold_app`.
41
28
 
42
29
  ## More
43
30
 
44
- - [MCP overview](/docs/mcp) — how the transport works.
31
+ - [MCP overview](/docs/mcp) — what the server exposes and how the transport works.
45
32
  - [Claude Code setup](/docs/mcp/claude-code) — CLI install.
46
33
  - [Cursor setup](/docs/mcp/cursor) — same JSON shape.
package/docs/mcp.md CHANGED
@@ -1,44 +1,121 @@
1
1
  # Model Context Protocol
2
2
 
3
- Ablo ships an MCP server at `/api/mcp`. Connect any MCP-compatible AI
4
- assistant — Claude Code, Cursor, Windsurf — and your sync models become
5
- typed, callable tools.
3
+ Ablo publishes **two** MCP servers for two different jobs. Don't confuse them:
6
4
 
7
- ## Install
5
+ | Server | Purpose | Auth | Tools |
6
+ |---|---|---|---|
7
+ | **Coordination** (`@ablo/mcp`) | Let an agent safely read & mutate your application data | API key (`sk_…` / `rk_…`) | per-model `get` / `list` / `create` / `update` / `delete` / `claim` / `release` |
8
+ | **Integration-helper** (hosted `/api/mcp`) | Help an AI coding assistant write SDK integration code that compiles | none (public docs) | doc search, export surface, schema lint, scaffold |
8
9
 
9
- Pick your client:
10
+ The coordination server **is the data plane** — it is how an agent changes
11
+ state. The integration-helper server only serves docs, schema lint, and
12
+ scaffolds; it does **not** read or write application data (there are no
13
+ per-model data tools on it). Pick by what you're doing: shipping an agent that
14
+ edits rows → coordination; teaching your IDE assistant the SDK → helper.
15
+
16
+ ## Coordination server (`@ablo/mcp`)
17
+
18
+ The coordination server is the MCP projection of the model-scoped API
19
+ (`/v1/models/...`) — the same surface as `ablo.<model>.create/update/claim` and
20
+ the REST routes, rendered as tools. An agent connects with your API key and
21
+ gets one safe loop: **claim → read → commit → release.**
22
+
23
+ Install over stdio; set your key in the host's MCP env:
24
+
25
+ ```bash
26
+ claude mcp add ablo -- npx -y @ablo/mcp
27
+ # env: ABLO_API_KEY=sk_… (ABLO_API_URL optional; defaults to the hosted API)
28
+ ```
29
+
30
+ Each tool mirrors an SDK verb, scoped to a model + id:
31
+
32
+ | Tool | Mirrors | Does |
33
+ |---|---|---|
34
+ | `get_model` | `ablo.<model>.get(id)` | read latest state + active claims |
35
+ | `list_models` | `ablo.<model>.list({…})` | cursor-paginated list with filters |
36
+ | `create_model` | `ablo.<model>.create({ data })` | guarded create |
37
+ | `update_model` | `ablo.<model>.update({ id, … })` | guarded update |
38
+ | `delete_model` | `ablo.<model>.delete({ id })` | guarded delete |
39
+ | `claim_model` | `ablo.<model>.claim({ id })` | acquire / queue a coordination lease |
40
+ | `release_claim` | — | release the lease so others proceed |
41
+
42
+ The agent-facing contract — the safe loop, the "derive idempotency keys from
43
+ the business event" rule, and the error-code playbook — ships as a loadable
44
+ skill at `@ablo/mcp/skill.md`. Lives in `packages/mcp/`
45
+ (`createCoordinationMcpServer`, `src/tools.ts`).
46
+
47
+ ## Integration-helper server
48
+
49
+ If you're integrating `@abloatai/ablo` with the help of an AI coding
50
+ assistant (Claude Code, Cursor, Windsurf, Codex), you don't want it guessing
51
+ at the API. This hosted server lets the assistant search the real docs,
52
+ inspect the actual export surface, lint your schema, and scaffold a starter —
53
+ so the code it writes uses APIs that exist. It serves docs only and returns
54
+ nothing org-specific; data access happens through the SDK or the coordination
55
+ server above, never here.
56
+
57
+ > The `@abloatai/ablo` npm package itself bundles neither server — it has
58
+ > no `@modelcontextprotocol/sdk` dependency. The helper is a feature of Ablo's
59
+ > hosted app, mounted at `/api/mcp`; the coordination server is the separate
60
+ > `@ablo/mcp` package.
61
+
62
+ ### Install
63
+
64
+ Point your assistant at the hosted endpoint — no auth, no token:
65
+
66
+ ```bash
67
+ claude mcp add --transport http ablo https://<your-app>/api/mcp
68
+ ```
69
+
70
+ Per-client walkthroughs:
10
71
 
11
72
  - [Claude Code](/docs/mcp/claude-code)
12
73
  - [Cursor](/docs/mcp/cursor)
13
74
  - [Windsurf](/docs/mcp/windsurf)
14
75
 
15
- ## How it works
76
+ ### What it exposes
16
77
 
17
- Each model you declare becomes one or more MCP tools:
78
+ #### Tools
18
79
 
19
- | Model method | MCP tool name | What it does |
20
- |---|---|---|
21
- | `retrieve` | `<model>.retrieve` | Returns the row + a stamp. |
22
- | `list` | `<model>.list` | Cursor-paginated discovery. |
23
- | `update` | `<model>.update` | Write, requires the prior stamp. |
24
- | `<model>.claim` | `claim.create` | Claim a row before writing, then release when held work finishes. |
80
+ | Tool | What it does |
81
+ |---|---|
82
+ | `search_ablo_docs` | Keyword search across the docs corpus. Returns ranked matches with excerpts. Follow up with `get_recipe` on the top hit. |
83
+ | `get_recipe` | Returns the full markdown of one doc by name (e.g. `readme`, `quickstart`, `schema-contract`, `integration-guide`, `api`, `guarantees`). |
84
+ | `get_api_surface` | Returns the structured export list for an SDK subpath (`@abloatai/ablo`, `./react`, `./schema`, `./testing`, …). Call with no argument to list every subpath. |
85
+ | `validate_schema` | Lints `defineSchema` source against the DSL rules (camelCase fields, lowercase model keys, `scope`/`grants` sync groups, valid `load` strategies, no legacy builders) and returns a structured issue list. Runs no code. |
86
+ | `scaffold_app` | Emits a starter file tree for a schema-first integration — `next`, `node-agent`, or `plain`, with `managed` or `data-source` storage. |
87
+
88
+ #### Resources
89
+
90
+ Every doc file is addressable at `ablo://docs/{name}`, so a
91
+ client can list the corpus and fetch individual files on demand instead of
92
+ loading everything into context.
93
+
94
+ #### Prompts
25
95
 
26
- The assistant gets typed JSON schemas, real argument types, and typed
27
- rejections when it writes stale state. No invention, no hallucinated IDs.
96
+ Reusable, parameterised templates that drive an end-to-end flow:
28
97
 
29
- ## Auth
98
+ - `integrate-sync-engine` — wire the SDK into an existing project.
99
+ - `add-agent` — add an agent worker that coordinates via intents and
100
+ conflict-safe writes.
101
+ - `define-schema` — design a Zod-first schema from a description, then run
102
+ `validate_schema` before committing.
30
103
 
31
- The MCP transport uses a scoped bearer token issued by your server. Pass that
32
- token into the MCP client's auth header configuration. See your client's setup
33
- guide for the exact mechanism.
104
+ ### Transport and limits
34
105
 
35
- ## Limits
106
+ The endpoint uses the stateless Streamable HTTP transport (`POST /api/mcp`;
107
+ `GET` returns 405 — SSE is not supported in stateless mode). A fresh
108
+ server is built per request, which suits serverless and horizontally-scaled
109
+ deployments.
36
110
 
37
- The MCP endpoint is rate-limited per token. Read-heavy bursts are fine;
38
- write-heavy bursts get throttled at the dashboard-configured cap.
111
+ There is **no authentication**: the server only serves docs, schema lint,
112
+ and scaffolds, so there's nothing org-scoped to protect. Abuse is bounded
113
+ by IP-based rate limiting — 120 requests per minute per IP. Rate-limit
114
+ headers are echoed on every response.
39
115
 
40
- ## More
116
+ ### Where it lives
41
117
 
42
- The [MCP landing page](/mcp) has the product pitch. The route handler
43
- itself is at `apps/sync-web/src/app/api/mcp/route.ts` if you want to read
44
- the implementation.
118
+ - Route handler: `apps/sync-web/src/app/api/mcp/route.ts`
119
+ - Server setup: `apps/sync-web/src/lib/mcp-ablo/server.ts`
120
+ (`createSyncEngineMcpServer`)
121
+ - Tools: `apps/sync-web/src/lib/mcp-ablo/tools/`
@@ -1,29 +1,26 @@
1
1
  # Quickstart
2
2
 
3
- Declare your state, create one row, and make one confirmed update.
3
+ Start building with Ablo in two steps: install, then declare one model and write
4
+ through its generated client.
4
5
 
5
6
  If you already have a backend and database, still start here. The SDK call shape
6
7
  is the same; [Integration Guide](./integration-guide.md) explains when to use
7
8
  Ablo-managed state versus a Data Source that calls your existing API service.
8
9
 
9
- ## 1. Install
10
+ ## 1. Install and set a sandbox key
10
11
 
11
12
  ```bash
12
13
  npm install @abloatai/ablo
13
- ```
14
-
15
- ## 2. Set a Sandbox Key
16
-
17
- Use an Ablo sandbox key while integrating.
18
-
19
- ```bash
20
14
  export ABLO_API_KEY=sk_test_...
21
15
  ```
22
16
 
23
17
  `ABLO_API_KEY` is for trusted server runtimes. Browser apps should use the React
24
18
  provider with a scoped session route, not a bundled API key.
25
19
 
26
- ## 3. Declare a Schema
20
+ ## 2. Declare schema and write state
21
+
22
+ Your schema is the contract. It generates `ablo.<model>` methods for app code,
23
+ server actions, agents, React reads, and Data Source requests.
27
24
 
28
25
  ```ts
29
26
  import Ablo from '@abloatai/ablo';
@@ -41,25 +38,21 @@ export const ablo = Ablo({
41
38
  schema,
42
39
  apiKey: process.env.ABLO_API_KEY,
43
40
  });
44
- ```
45
-
46
- Customer apps should always pass `schema`. Treat it like Prisma's schema file:
47
- it is the source of truth for typed model clients, realtime subscriptions,
48
- agent writes, and Data Source requests.
49
-
50
- ## 4. Create and Update
51
-
52
- ```ts
53
41
  await ablo.ready();
54
42
 
55
43
  const created = await ablo.weatherReports.create({
56
- location: 'Stockholm',
57
- status: 'pending',
44
+ data: {
45
+ location: 'Stockholm',
46
+ status: 'pending',
47
+ },
58
48
  });
59
49
 
60
- const updated = await ablo.weatherReports.update(created.id, {
61
- status: 'ready',
62
- forecast: 'Light rain, 13C',
50
+ const updated = await ablo.weatherReports.update({
51
+ id: created.id,
52
+ data: {
53
+ status: 'ready',
54
+ forecast: 'Light rain, 13C',
55
+ },
63
56
  });
64
57
 
65
58
  console.log({ id: updated.id, status: updated.status });
@@ -71,77 +64,86 @@ Expected output:
71
64
  { id: '...', status: 'ready' }
72
65
  ```
73
66
 
74
- ## 5. Run the Example
67
+ ## Run the example
75
68
 
76
69
  ```bash
77
- cd examples
78
- ABLO_API_KEY=sk_test_... npx tsx quickstart.ts
70
+ cd packages/sync-engine
71
+ ABLO_API_KEY=sk_test_... npx tsx examples/quickstart.ts
79
72
  ```
80
73
 
81
- ## 6. AI Activity on Existing State
74
+ ## Add coordination for slow work
82
75
 
83
76
  When AI or background work will touch an existing row for more than a quick
84
- write, coordinate through the flat model verbs: `ablo.<model>.claim(id, ...)` to
85
- claim the row (returns the row), `ablo.<model>.claimState(id)` to read who's working
86
- on it (synchronous; never blocks), and the normal `ablo.<model>.update(id, ...)`
87
- to write. Normal reads still work while the claim is held; server reads can opt
88
- into `ifClaimed: 'wait'` or `ifClaimed: 'fail'` when they should not read through
89
- active work. The callback form releases the claim when the callback returns or
90
- throws.
77
+ write, coordinate through `claim({ id })`. It claims the row and hands a handle
78
+ back; `claim.state({ id })` reads who is currently working on it without blocking;
79
+ and you write the usual way with `ablo.<model>.update({ id, data })`.
80
+
81
+ Claims don't lock. If another writer holds the row, `claim` waits for them,
82
+ re-reads the fresh row, then hands it to you so two writers serialize instead
83
+ of clobbering. Normal reads still work while the claim is held. If a server read
84
+ should not return a row while someone else is mid-edit, pass `ifClaimed: 'wait'`
85
+ to wait for the claim to clear, or `ifClaimed: 'fail'` to error out instead.
86
+ Call `handle.release()` when your work is done.
91
87
 
92
88
  ```ts
93
89
  // Claim the row so other participants serialize behind us while we work.
94
- await ablo.weatherReports.claim(
95
- 'weather_stockholm',
96
- async (report) => {
97
- // Your existing weather tool or agent call. While this runs, other clients
98
- // see that weather_stockholm is being checked.
99
- const weather = await weatherAgent.getWeather(report.location);
100
-
101
- await ablo.weatherReports.update(report.id, {
102
- status: 'ready',
103
- forecast: weather.summary,
104
- });
90
+ const handle = await ablo.weatherReports.claim({
91
+ id: 'weather_stockholm',
92
+ action: 'checking_weather',
93
+ ttl: '2m',
94
+ });
95
+
96
+ // Your existing weather tool or agent call. While this runs, other clients
97
+ // see that weather_stockholm is being checked.
98
+ const weather = await weatherAgent.getWeather(handle.data.location);
99
+
100
+ await ablo.weatherReports.update({
101
+ id: handle.data.id,
102
+ data: {
103
+ status: 'ready',
104
+ forecast: weather.summary,
105
105
  },
106
- { action: 'checking_weather', ttl: '2m' },
107
- );
106
+ });
107
+
108
+ await handle.release();
108
109
  ```
109
110
 
110
- Ablo does not fetch the weather. The claim is **advisory**: if another
111
- participant already holds the row, `claim` waits for them to finish and re-reads
112
- before handing back the row. While you hold the claim, `update(id, ...)` is
113
- stale-guarded and rejects with `AbloStaleContextError` if the row changed under
114
- you. The claim releases automatically once the callback returns or throws.
111
+ Ablo does not fetch the weather. If another participant already holds the row,
112
+ `claim` waits for them to finish, re-reads, and then hands you the fresh row.
113
+ While you hold the claim, `update({ id, data })` rejects with `AbloStaleContextError`
114
+ if someone else changed the row first — so you never overwrite work you didn't
115
+ see. Call `handle.release()` once your work is done.
115
116
 
116
- ## 7. Multiplayer and Claimed Work
117
+ ## Multiplayer and claimed work
117
118
 
118
119
  There is no separate multiplayer mode. Use the same schema client for human UI,
119
120
  server actions, and agents; Ablo fans out confirmed writes and keeps active
120
121
  claims visible on the same model row.
121
122
 
122
- `claimState(id)` tells you when another human or agent is active on the same row.
123
- For schema clients, `claim(id, work)` waits fairly, re-reads, and then lets you
123
+ `claim.state({ id })` tells you when another human or agent is active on the same row.
124
+ For schema clients, `claim({ id })` waits fairly, re-reads, and then lets you
124
125
  write through the model.
125
126
 
126
127
  ```ts
127
- const active = ablo.weatherReports.claimState('weather_stockholm');
128
+ const active = ablo.weatherReports.claim.state({ id: 'weather_stockholm' });
128
129
  if (active) {
129
130
  console.log(`${active.heldBy} is ${active.action}`);
130
131
  }
131
132
 
132
- await ablo.weatherReports.claim('weather_stockholm', async (report) => {
133
- await ablo.weatherReports.update(report.id, { status: 'ready' });
134
- });
133
+ const handle = await ablo.weatherReports.claim({ id: 'weather_stockholm' });
134
+ await ablo.weatherReports.update({ id: handle.data.id, data: { status: 'ready' } });
135
+ await handle.release();
135
136
  ```
136
137
 
137
138
  Use `{ wait: false }` on `claim` when work should be skipped instead of queued
138
139
  behind an active holder.
139
140
 
140
- ## 8. Next Steps
141
+ ## Next steps
141
142
 
142
143
  Keep using the schema client for app and agent writes.
143
144
 
144
145
  - [Integration Guide](./integration-guide.md) explains the full app, React, Data Source, multiplayer, and agent path.
146
+ - [Schema Contract](./schema-contract.md) explains what the schema drives across SDK, React, agents, Data Source, and schema push.
145
147
  - [Guarantees](./guarantees.md) explains what confirmed writes and stale checks mean.
146
148
  - [Client Behavior](./client-behavior.md) covers errors, retries, and public imports.
147
149
  - [Connect Your Database](./data-sources.md) covers the optional route for teams keeping rows in their own database.
package/docs/react.md CHANGED
@@ -56,7 +56,7 @@ export function Providers({
56
56
  | `url` | hosted endpoint | WebSocket URL of the sync server (`wss://…`). Hosted apps omit it. |
57
57
  | `apiKey` | session/cookie | Bootstrap auth. Browser apps **omit this** — the key stays server-side. See Identity below. |
58
58
  | `fallback` | neutral spinner | Rendered during the *first* bootstrap only. Pass a branded skeleton, `null`, or `'passthrough'`. |
59
- | `bootstrapMode` | `'full'` | `'full'` pulls the org's baseline before ready; `'none'` skips the baseline and processes live deltas only.|
59
+ | `bootstrapMode` | `'full'` | `'full'` loads existing rows before the app renders, so reads are populated on first paint; `'none'` skips that initial load and only streams changes as they happen.|
60
60
  | `persistence` | `'volatile'` | `'indexeddb'` opts into a durable browser cache that survives reloads. |
61
61
  | `onSessionExpired` | — | Fired after the engine has already purged on a rejected session — use for redirect-to-sign-in. |
62
62
  | `onError` | — | Engine / WebSocket / `postBootstrap` errors. Wire to Sentry / Datadog. |
@@ -74,8 +74,8 @@ reaches the browser, is the whole of
74
74
  import { useAblo } from '@abloatai/ablo/react';
75
75
 
76
76
  export function ReportView({ report: serverReport }: { report: { id: string; location: string } }) {
77
- const report = useAblo((ablo) => ablo.weatherReports.retrieve(serverReport.id)) ?? serverReport;
78
- const active = useAblo((ablo) => ablo.weatherReports.claimState(serverReport.id));
77
+ const report = useAblo((ablo) => ablo.weatherReports.get(serverReport.id)) ?? serverReport;
78
+ const active = useAblo((ablo) => ablo.weatherReports.claim.state({ id: serverReport.id }));
79
79
  const claimed = Boolean(active);
80
80
 
81
81
  return <article>{report.location}</article>;
@@ -84,12 +84,13 @@ export function ReportView({ report: serverReport }: { report: { id: string; loc
84
84
 
85
85
  The hook:
86
86
 
87
- 1. Reads through the same `ablo.<model>` methods as the rest of the SDK.
87
+ 1. Uses the same `ablo.<model>.get(id)` / `.getAll()` methods you'd call anywhere
88
+ else in the SDK — the hook just makes them reactive.
88
89
  2. Tracks the model fields read by the selector and re-renders when confirmed
89
90
  deltas arrive.
90
91
  3. Lets Server Component data stay outside the hook: use `?? serverReport` when a
91
92
  parent already loaded the row.
92
- 4. Works for coordination state too, such as `ablo.weatherReports.claimState(id)`.
93
+ 4. Works for coordination state too, such as `ablo.weatherReports.claim.state({ id })`.
93
94
 
94
95
  Use the zero-argument form only when you need the full client for callbacks,
95
96
  effects, or writes:
@@ -98,15 +99,14 @@ effects, or writes:
98
99
  const abloClient = useAblo();
99
100
  ```
100
101
 
101
- Prefer selector reads like `useAblo((ablo) => ablo.<model>.retrieve(id))`.
102
- String model names are kept on older hooks for compatibility, but first examples
103
- should use the same model-client shape as the rest of the SDK.
102
+ Prefer selector reads like `useAblo((ablo) => ablo.<model>.get(id))`. Older hooks
103
+ also accept a string model name; prefer the selector form shown above.
104
104
 
105
105
  For collections, keep the selector on the model client too:
106
106
 
107
107
  ```tsx
108
108
  const reports = useAblo((ablo) =>
109
- ablo.weatherReports.list({
109
+ ablo.weatherReports.getAll({
110
110
  where: { projectId },
111
111
  filter: (report) => report.status !== 'ready',
112
112
  state: 'live',
@@ -117,10 +117,14 @@ const reports = useAblo((ablo) =>
117
117
  ## Server Load
118
118
 
119
119
  ```tsx
120
- const [report] = await ablo.weatherReports.load({ where: { id } });
120
+ const report = await ablo.weatherReports.retrieve({ id });
121
121
  ```
122
122
 
123
- Use `load` in Server Components when the row may not be in the local pool yet.
123
+ Use `retrieve` in Server Components when the row may not be in the local pool
124
+ yet — it hydrates from the local store and the server, and returns a Promise, so
125
+ `await` it. (Server reads come in two shapes: `retrieve({ id })` for one row and
126
+ `list({ where })` for many; both are async. The synchronous local reads are
127
+ `get`/`getAll`/`getCount`, used in render below.)
124
128
 
125
129
  ## Writes
126
130
 
@@ -130,7 +134,9 @@ For Server Actions and route handlers, call the SDK directly:
130
134
  import { ablo } from '@/lib/ablo';
131
135
 
132
136
  const snap = ablo.snapshot({ weatherReports: id });
133
- await ablo.weatherReports.update(id, patch, {
137
+ await ablo.weatherReports.update({
138
+ id,
139
+ data: patch,
134
140
  readAt: snap.stamp,
135
141
  onStale: 'reject',
136
142
  wait: 'confirmed',
@@ -146,11 +152,13 @@ const ablo = useAblo();
146
152
  async function markReady() {
147
153
  if (!ablo) return;
148
154
  const snap = ablo.snapshot({ weatherReports: id });
149
- await ablo.weatherReports.update(
155
+ await ablo.weatherReports.update({
150
156
  id,
151
- { status: 'ready' },
152
- { readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
153
- );
157
+ data: { status: 'ready' },
158
+ readAt: snap.stamp,
159
+ onStale: 'reject',
160
+ wait: 'confirmed',
161
+ });
154
162
  }
155
163
  ```
156
164
 
package/docs/roadmap.md CHANGED
@@ -5,22 +5,22 @@ What is shipped, what is next, and what we will not build.
5
5
  ## Shipped
6
6
 
7
7
  - **Models, claims, commits** — the core API.
8
- - **Audit log** — hash-chained per principal, with `delegationChainRoot`.
8
+ - **Audit log** — a tamper-evident, hash-chained record of who did what,
9
+ per principal.
9
10
  - **MCP transport** — HTTP server at `/api/mcp`.
10
11
  - **TypeScript SDK** — `@abloatai/ablo`, with React bindings.
11
12
  - **Dashboard** — keys, audit, metrics, allowed origins.
12
- - **Schema migrations** — declarative model schema changes. Pure
13
- diff/classify/cast-safety/backfill planning engine (`diffSchema`,
14
- `classifyMigration`, `classifyCast`, `isAutoApplicable`) ships in the SDK;
15
- `ablo generate` emits typed clients from a pushed schema; the server
16
- applies and activates migrations only on success.
17
- - **Structured error contract** versioned (`ERROR_CONTRACT_VERSION`),
18
- drift-guarded code registry shared across the HTTP, WebSocket, and MCP
19
- planes, with always-on request ids and an OpenAPI error envelope.
20
- - **Relation-driven sync groups** — membership-routed fan-out via branded
21
- `SyncGroup` + schema-declared identity roles (schema-JSON v2).
22
- - **Intent coordination plane** — Stripe-shaped `Intent` with per-model
23
- `intent(id)` handles for lease/await/commit coordination between agents.
13
+ - **Schema migrations** — change a model's shape after it already has data.
14
+ Ablo plans the migration, tells you which changes are safe to auto-apply,
15
+ and backfills the rest; the server applies and activates a migration only
16
+ if it succeeds. `ablo generate` emits typed clients from a pushed schema.
17
+ - **Structured errors** every error has a stable code and a request id,
18
+ and the same codes work whether you reach Ablo over HTTP, WebSocket, or MCP.
19
+ - **Sync groups** clients automatically receive only the records they have
20
+ access to, based on relationships you declare in the schema.
21
+ - **Agent coordination** — when several agents work on the same model, they
22
+ take turns instead of overwriting each other, via `intent(id)` handles
23
+ that claim, wait, and commit.
24
24
 
25
25
  ## In flight
26
26