@abloatai/ablo 0.3.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 (278) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +230 -0
  5. package/dist/BaseSyncedStore.d.ts +709 -0
  6. package/dist/BaseSyncedStore.js +1843 -0
  7. package/dist/Database.d.ts +344 -0
  8. package/dist/Database.js +1259 -0
  9. package/dist/LazyReferenceCollection.d.ts +181 -0
  10. package/dist/LazyReferenceCollection.js +460 -0
  11. package/dist/Model.d.ts +339 -0
  12. package/dist/Model.js +715 -0
  13. package/dist/ModelRegistry.d.ts +200 -0
  14. package/dist/ModelRegistry.js +535 -0
  15. package/dist/NetworkMonitor.d.ts +27 -0
  16. package/dist/NetworkMonitor.js +73 -0
  17. package/dist/ObjectPool.d.ts +202 -0
  18. package/dist/ObjectPool.js +1106 -0
  19. package/dist/SyncClient.d.ts +489 -0
  20. package/dist/SyncClient.js +1555 -0
  21. package/dist/SyncEngineContext.d.ts +46 -0
  22. package/dist/SyncEngineContext.js +74 -0
  23. package/dist/adapters/alwaysOnline.d.ts +16 -0
  24. package/dist/adapters/alwaysOnline.js +19 -0
  25. package/dist/adapters/inMemoryStorage.d.ts +30 -0
  26. package/dist/adapters/inMemoryStorage.js +94 -0
  27. package/dist/agent/Agent.d.ts +358 -0
  28. package/dist/agent/Agent.js +500 -0
  29. package/dist/agent/index.d.ts +115 -0
  30. package/dist/agent/index.js +128 -0
  31. package/dist/agent/session.d.ts +90 -0
  32. package/dist/agent/session.js +156 -0
  33. package/dist/agent/types.d.ts +73 -0
  34. package/dist/agent/types.js +10 -0
  35. package/dist/ai-sdk/coordination-context.d.ts +51 -0
  36. package/dist/ai-sdk/coordination-context.js +107 -0
  37. package/dist/ai-sdk/index.d.ts +68 -0
  38. package/dist/ai-sdk/index.js +68 -0
  39. package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
  40. package/dist/ai-sdk/intent-broadcast.js +72 -0
  41. package/dist/ai-sdk/wrap.d.ts +67 -0
  42. package/dist/ai-sdk/wrap.js +45 -0
  43. package/dist/api/index.d.ts +10 -0
  44. package/dist/api/index.js +9 -0
  45. package/dist/auth/index.d.ts +137 -0
  46. package/dist/auth/index.js +246 -0
  47. package/dist/client/Ablo.d.ts +835 -0
  48. package/dist/client/Ablo.js +1440 -0
  49. package/dist/client/ApiClient.d.ts +200 -0
  50. package/dist/client/ApiClient.js +659 -0
  51. package/dist/client/auth.d.ts +79 -0
  52. package/dist/client/auth.js +81 -0
  53. package/dist/client/createInternalComponents.d.ts +44 -0
  54. package/dist/client/createInternalComponents.js +88 -0
  55. package/dist/client/createModelProxy.d.ts +152 -0
  56. package/dist/client/createModelProxy.js +199 -0
  57. package/dist/client/identity.d.ts +63 -0
  58. package/dist/client/identity.js +156 -0
  59. package/dist/client/index.d.ts +36 -0
  60. package/dist/client/index.js +33 -0
  61. package/dist/client/persistence.d.ts +7 -0
  62. package/dist/client/persistence.js +11 -0
  63. package/dist/client/validateAbloOptions.d.ts +42 -0
  64. package/dist/client/validateAbloOptions.js +43 -0
  65. package/dist/config/index.d.ts +10 -0
  66. package/dist/config/index.js +12 -0
  67. package/dist/context.d.ts +27 -0
  68. package/dist/context.js +58 -0
  69. package/dist/core/DatabaseManager.d.ts +108 -0
  70. package/dist/core/DatabaseManager.js +361 -0
  71. package/dist/core/QueryProcessor.d.ts +77 -0
  72. package/dist/core/QueryProcessor.js +262 -0
  73. package/dist/core/QueryView.d.ts +64 -0
  74. package/dist/core/QueryView.js +219 -0
  75. package/dist/core/StoreManager.d.ts +131 -0
  76. package/dist/core/StoreManager.js +334 -0
  77. package/dist/core/ViewRegistry.d.ts +20 -0
  78. package/dist/core/ViewRegistry.js +55 -0
  79. package/dist/core/index.d.ts +34 -0
  80. package/dist/core/index.js +59 -0
  81. package/dist/core/openIDBWithTimeout.d.ts +27 -0
  82. package/dist/core/openIDBWithTimeout.js +63 -0
  83. package/dist/core/query-utils.d.ts +37 -0
  84. package/dist/core/query-utils.js +60 -0
  85. package/dist/errors.d.ts +235 -0
  86. package/dist/errors.js +243 -0
  87. package/dist/index.d.ts +41 -0
  88. package/dist/index.js +82 -0
  89. package/dist/interfaces/headless.d.ts +95 -0
  90. package/dist/interfaces/headless.js +41 -0
  91. package/dist/interfaces/index.d.ts +321 -0
  92. package/dist/interfaces/index.js +8 -0
  93. package/dist/mutators/RecordingTransaction.d.ts +36 -0
  94. package/dist/mutators/RecordingTransaction.js +216 -0
  95. package/dist/mutators/Transaction.d.ts +48 -0
  96. package/dist/mutators/Transaction.js +64 -0
  97. package/dist/mutators/UndoManager.d.ts +114 -0
  98. package/dist/mutators/UndoManager.js +143 -0
  99. package/dist/mutators/defineMutators.d.ts +55 -0
  100. package/dist/mutators/defineMutators.js +28 -0
  101. package/dist/policy/index.d.ts +19 -0
  102. package/dist/policy/index.js +18 -0
  103. package/dist/policy/types.d.ts +74 -0
  104. package/dist/policy/types.js +17 -0
  105. package/dist/principal.d.ts +44 -0
  106. package/dist/principal.js +49 -0
  107. package/dist/query/client.d.ts +43 -0
  108. package/dist/query/client.js +84 -0
  109. package/dist/query/index.d.ts +6 -0
  110. package/dist/query/index.js +5 -0
  111. package/dist/query/types.d.ts +143 -0
  112. package/dist/query/types.js +36 -0
  113. package/dist/react/AbloProvider.d.ts +205 -0
  114. package/dist/react/AbloProvider.js +398 -0
  115. package/dist/react/ClientSideSuspense.d.ts +36 -0
  116. package/dist/react/ClientSideSuspense.js +17 -0
  117. package/dist/react/DefaultFallback.d.ts +24 -0
  118. package/dist/react/DefaultFallback.js +43 -0
  119. package/dist/react/SyncGroupProvider.d.ts +19 -0
  120. package/dist/react/SyncGroupProvider.js +44 -0
  121. package/dist/react/context.d.ts +161 -0
  122. package/dist/react/context.js +35 -0
  123. package/dist/react/index.d.ts +64 -0
  124. package/dist/react/index.js +73 -0
  125. package/dist/react/internalContext.d.ts +35 -0
  126. package/dist/react/internalContext.js +3 -0
  127. package/dist/react/useAblo.d.ts +72 -0
  128. package/dist/react/useAblo.js +63 -0
  129. package/dist/react/useCurrentUserId.d.ts +21 -0
  130. package/dist/react/useCurrentUserId.js +33 -0
  131. package/dist/react/useErrorListener.d.ts +20 -0
  132. package/dist/react/useErrorListener.js +39 -0
  133. package/dist/react/useIntent.d.ts +29 -0
  134. package/dist/react/useIntent.js +42 -0
  135. package/dist/react/useMutate.d.ts +83 -0
  136. package/dist/react/useMutate.js +122 -0
  137. package/dist/react/useMutationFailureListener.d.ts +26 -0
  138. package/dist/react/useMutationFailureListener.js +38 -0
  139. package/dist/react/useMutators.d.ts +56 -0
  140. package/dist/react/useMutators.js +66 -0
  141. package/dist/react/usePresence.d.ts +32 -0
  142. package/dist/react/usePresence.js +41 -0
  143. package/dist/react/useQuery.d.ts +123 -0
  144. package/dist/react/useQuery.js +145 -0
  145. package/dist/react/useReactive.d.ts +35 -0
  146. package/dist/react/useReactive.js +111 -0
  147. package/dist/react/useReader.d.ts +69 -0
  148. package/dist/react/useReader.js +73 -0
  149. package/dist/react/useSyncStatus.d.ts +61 -0
  150. package/dist/react/useSyncStatus.js +76 -0
  151. package/dist/react/useUndoScope.d.ts +36 -0
  152. package/dist/react/useUndoScope.js +73 -0
  153. package/dist/realtime/index.d.ts +10 -0
  154. package/dist/realtime/index.js +9 -0
  155. package/dist/schema/field.d.ts +134 -0
  156. package/dist/schema/field.js +264 -0
  157. package/dist/schema/index.d.ts +29 -0
  158. package/dist/schema/index.js +38 -0
  159. package/dist/schema/model.d.ts +326 -0
  160. package/dist/schema/model.js +89 -0
  161. package/dist/schema/queries.d.ts +203 -0
  162. package/dist/schema/queries.js +145 -0
  163. package/dist/schema/relation.d.ts +172 -0
  164. package/dist/schema/relation.js +104 -0
  165. package/dist/schema/schema.d.ts +259 -0
  166. package/dist/schema/schema.js +188 -0
  167. package/dist/schema/sugar.d.ts +129 -0
  168. package/dist/schema/sugar.js +94 -0
  169. package/dist/source/index.d.ts +423 -0
  170. package/dist/source/index.js +320 -0
  171. package/dist/source/pushQueue.d.ts +112 -0
  172. package/dist/source/pushQueue.js +249 -0
  173. package/dist/stores/ObjectStore.d.ts +103 -0
  174. package/dist/stores/ObjectStore.js +371 -0
  175. package/dist/stores/ObjectStoreContract.d.ts +39 -0
  176. package/dist/stores/ObjectStoreContract.js +1 -0
  177. package/dist/stores/SyncActionStore.d.ts +101 -0
  178. package/dist/stores/SyncActionStore.js +481 -0
  179. package/dist/sync/BootstrapHelper.d.ts +127 -0
  180. package/dist/sync/BootstrapHelper.js +434 -0
  181. package/dist/sync/ConnectionManager.d.ts +136 -0
  182. package/dist/sync/ConnectionManager.js +465 -0
  183. package/dist/sync/HydrationCoordinator.d.ts +137 -0
  184. package/dist/sync/HydrationCoordinator.js +468 -0
  185. package/dist/sync/NetworkProbe.d.ts +43 -0
  186. package/dist/sync/NetworkProbe.js +113 -0
  187. package/dist/sync/OfflineFlush.d.ts +9 -0
  188. package/dist/sync/OfflineFlush.js +22 -0
  189. package/dist/sync/OfflineTransactionStore.d.ts +37 -0
  190. package/dist/sync/OfflineTransactionStore.js +263 -0
  191. package/dist/sync/SyncWebSocket.d.ts +663 -0
  192. package/dist/sync/SyncWebSocket.js +1336 -0
  193. package/dist/sync/createIntentStream.d.ts +33 -0
  194. package/dist/sync/createIntentStream.js +243 -0
  195. package/dist/sync/createPresenceStream.d.ts +46 -0
  196. package/dist/sync/createPresenceStream.js +192 -0
  197. package/dist/sync/createSnapshot.d.ts +33 -0
  198. package/dist/sync/createSnapshot.js +124 -0
  199. package/dist/sync/participants.d.ts +114 -0
  200. package/dist/sync/participants.js +336 -0
  201. package/dist/sync/schemas.d.ts +79 -0
  202. package/dist/sync/schemas.js +78 -0
  203. package/dist/testing/fixtures/bootstrap.d.ts +45 -0
  204. package/dist/testing/fixtures/bootstrap.js +53 -0
  205. package/dist/testing/fixtures/deltas.d.ts +86 -0
  206. package/dist/testing/fixtures/deltas.js +139 -0
  207. package/dist/testing/fixtures/models.d.ts +82 -0
  208. package/dist/testing/fixtures/models.js +270 -0
  209. package/dist/testing/helpers/react-wrapper.d.ts +66 -0
  210. package/dist/testing/helpers/react-wrapper.js +64 -0
  211. package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
  212. package/dist/testing/helpers/sync-engine-harness.js +70 -0
  213. package/dist/testing/helpers/wait.d.ts +25 -0
  214. package/dist/testing/helpers/wait.js +44 -0
  215. package/dist/testing/index.d.ts +21 -0
  216. package/dist/testing/index.js +32 -0
  217. package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
  218. package/dist/testing/mocks/MockMutationExecutor.js +139 -0
  219. package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
  220. package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
  221. package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
  222. package/dist/testing/mocks/MockSyncContext.js +100 -0
  223. package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
  224. package/dist/testing/mocks/MockSyncStore.js +171 -0
  225. package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
  226. package/dist/testing/mocks/MockWebSocket.js +117 -0
  227. package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
  228. package/dist/transactions/OptimisticEchoTracker.js +104 -0
  229. package/dist/transactions/TransactionQueue.d.ts +499 -0
  230. package/dist/transactions/TransactionQueue.js +1895 -0
  231. package/dist/transactions/index.d.ts +16 -0
  232. package/dist/transactions/index.js +7 -0
  233. package/dist/transactions/mutation-error-handler.d.ts +5 -0
  234. package/dist/transactions/mutation-error-handler.js +39 -0
  235. package/dist/types/global.d.ts +107 -0
  236. package/dist/types/global.js +38 -0
  237. package/dist/types/index.d.ts +241 -0
  238. package/dist/types/index.js +70 -0
  239. package/dist/types/streams.d.ts +495 -0
  240. package/dist/types/streams.js +11 -0
  241. package/dist/utils/asyncIterator.d.ts +41 -0
  242. package/dist/utils/asyncIterator.js +142 -0
  243. package/dist/utils/duration.d.ts +28 -0
  244. package/dist/utils/duration.js +47 -0
  245. package/dist/utils/mobx-setup.d.ts +42 -0
  246. package/dist/utils/mobx-setup.js +381 -0
  247. package/docs/api-keys.md +24 -0
  248. package/docs/api.md +230 -0
  249. package/docs/audit.md +81 -0
  250. package/docs/capabilities.md +163 -0
  251. package/docs/client-behavior.md +202 -0
  252. package/docs/data-sources.md +214 -0
  253. package/docs/examples/agent-human.md +84 -0
  254. package/docs/examples/ai-sdk-tool.md +92 -0
  255. package/docs/examples/existing-python-backend.md +249 -0
  256. package/docs/examples/nextjs.md +88 -0
  257. package/docs/examples/server-agent.md +86 -0
  258. package/docs/guarantees.md +148 -0
  259. package/docs/index.md +97 -0
  260. package/docs/integration-guide.md +493 -0
  261. package/docs/interaction-model.md +140 -0
  262. package/docs/mcp/claude-code.md +43 -0
  263. package/docs/mcp/cursor.md +53 -0
  264. package/docs/mcp/windsurf.md +46 -0
  265. package/docs/mcp.md +59 -0
  266. package/docs/quickstart.md +152 -0
  267. package/docs/react.md +115 -0
  268. package/docs/roadmap.md +45 -0
  269. package/examples/README.md +54 -0
  270. package/examples/data-source/README.md +102 -0
  271. package/examples/data-source/ablo-driver.ts +89 -0
  272. package/examples/data-source/customer-server.ts +208 -0
  273. package/examples/data-source/run.ts +101 -0
  274. package/examples/data-source/schema.ts +25 -0
  275. package/examples/quickstart.ts +54 -0
  276. package/examples/tsconfig.json +16 -0
  277. package/llms.txt +143 -0
  278. package/package.json +147 -0
@@ -0,0 +1,53 @@
1
+ # Cursor
2
+
3
+ ## Install
4
+
5
+ Add the Ablo Sync MCP server to Cursor's `mcp.json`:
6
+
7
+ ```json
8
+ {
9
+ "mcpServers": {
10
+ "ablo-sync": {
11
+ "transport": "http",
12
+ "url": "https://<your-app>/api/mcp"
13
+ }
14
+ }
15
+ }
16
+ ```
17
+
18
+ The file lives at `~/.cursor/mcp.json` on macOS / Linux.
19
+
20
+ Restart Cursor. The Ablo Sync tools appear under the MCP icon in the agent
21
+ panel.
22
+
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
+ ## Verify
45
+
46
+ In Cursor's agent panel, open the MCP tools list. You should see the
47
+ Ablo Sync resource tools and their JSON schemas.
48
+
49
+ ## More
50
+
51
+ - [MCP overview](/docs/mcp) — how the transport works.
52
+ - [Claude Code setup](/docs/mcp/claude-code) — CLI install.
53
+ - [Windsurf setup](/docs/mcp/windsurf) — same JSON shape.
@@ -0,0 +1,46 @@
1
+ # Windsurf
2
+
3
+ ## Install
4
+
5
+ Add the Ablo Sync MCP server to Windsurf's MCP config:
6
+
7
+ ```json
8
+ {
9
+ "mcpServers": {
10
+ "ablo-sync": {
11
+ "transport": "http",
12
+ "url": "https://<your-app>/api/mcp"
13
+ }
14
+ }
15
+ }
16
+ ```
17
+
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
+ ```
36
+
37
+ ## Verify
38
+
39
+ Cascade's MCP panel lists every configured server with its tools. You
40
+ should see `ablo-sync` with resource tools enumerated.
41
+
42
+ ## More
43
+
44
+ - [MCP overview](/docs/mcp) — how the transport works.
45
+ - [Claude Code setup](/docs/mcp/claude-code) — CLI install.
46
+ - [Cursor setup](/docs/mcp/cursor) — same JSON shape.
package/docs/mcp.md ADDED
@@ -0,0 +1,59 @@
1
+ # Model Context Protocol
2
+
3
+ Ablo Sync ships an MCP server at `/api/mcp`. Connect any MCP-compatible AI
4
+ assistant — Claude Code, Cursor, Windsurf — and your sync resources become
5
+ typed, callable tools.
6
+
7
+ ## Install
8
+
9
+ Pick your client:
10
+
11
+ - [Claude Code](/docs/mcp/claude-code)
12
+ - [Cursor](/docs/mcp/cursor)
13
+ - [Windsurf](/docs/mcp/windsurf)
14
+
15
+ ## How it works
16
+
17
+ Each resource you declare becomes one or more MCP tools:
18
+
19
+ | Resource method | MCP tool name | What it does |
20
+ |---|---|---|
21
+ | `retrieve` | `<resource>.retrieve` | Returns the row + a stamp. |
22
+ | `list` | `<resource>.list` | Cursor-paginated discovery. |
23
+ | `update` | `<resource>.update` | Write, requires the prior stamp. |
24
+ | `intents.create` | `intent.create` | Declare a claim before writing. |
25
+
26
+ The assistant gets typed JSON schemas, real argument types, and typed
27
+ rejections when it writes stale state. No invention, no hallucinated IDs.
28
+
29
+ ## Auth
30
+
31
+ The MCP transport requires a capability token. Create one scoped to the
32
+ assistant's session:
33
+
34
+ ```ts
35
+ const capability = await ablo.capabilities.create({
36
+ participantKind: 'agent',
37
+ participantId: 'agent:claude-code',
38
+ // Strings derive from the schema's `identityRoles` templates
39
+ // (see integration-guide.md §1).
40
+ syncGroups: ['org:acme'],
41
+ operations: ['tasks.retrieve', 'tasks.update'],
42
+ label: 'claude-code dev session',
43
+ lease: '8h',
44
+ });
45
+ ```
46
+
47
+ Pass `capability.token` into the MCP client's auth header configuration.
48
+ See your client's setup guide for the exact mechanism.
49
+
50
+ ## Limits
51
+
52
+ The MCP endpoint is rate-limited per token. Read-heavy bursts are fine;
53
+ write-heavy bursts get throttled at the dashboard-configured cap.
54
+
55
+ ## More
56
+
57
+ The [MCP landing page](/mcp) has the product pitch. The route handler
58
+ itself is at `apps/sync-web/src/app/api/mcp/route.ts` if you want to read
59
+ the implementation.
@@ -0,0 +1,152 @@
1
+ # Quickstart
2
+
3
+ Declare your state, create one row, and make one confirmed update.
4
+
5
+ If you already have a backend and database, still start here. The SDK call shape
6
+ is the same; [Integration Guide](./integration-guide.md) explains when to use
7
+ Ablo-managed state versus a Data Source that calls your existing API service.
8
+
9
+ ## 1. Install
10
+
11
+ ```bash
12
+ npm install @ablo/sync-engine
13
+ ```
14
+
15
+ ## 2. Set a Sandbox Key
16
+
17
+ Use an Ablo sandbox key while integrating.
18
+
19
+ ```bash
20
+ export ABLO_API_KEY=sk_test_...
21
+ ```
22
+
23
+ `ABLO_API_KEY` is for trusted server runtimes. Browser apps should use the React
24
+ provider with a scoped capability/session route, not a bundled API key.
25
+
26
+ ## 3. Declare a Schema
27
+
28
+ ```ts
29
+ import Ablo from '@ablo/sync-engine';
30
+ import { defineSchema, model, z } from '@ablo/sync-engine/schema';
31
+
32
+ const schema = defineSchema({
33
+ weatherReports: model({
34
+ location: z.string(),
35
+ status: z.enum(['pending', 'ready']),
36
+ forecast: z.string().optional(),
37
+ }),
38
+ });
39
+
40
+ export const ablo = Ablo({
41
+ schema,
42
+ apiKey: process.env.ABLO_API_KEY,
43
+ });
44
+ ```
45
+
46
+ Pass `schema` for typed model resources. Omit it only for advanced server-side
47
+ resource clients such as custom agents and MCP routes.
48
+
49
+ ## 4. Create and Update
50
+
51
+ ```ts
52
+ await ablo.ready();
53
+
54
+ const created = await ablo.weatherReports.create({
55
+ location: 'Stockholm',
56
+ status: 'pending',
57
+ });
58
+
59
+ const updated = await ablo.weatherReports.update(created.id, {
60
+ status: 'ready',
61
+ forecast: 'Light rain, 13C',
62
+ });
63
+
64
+ console.log({ id: updated.id, status: updated.status });
65
+ ```
66
+
67
+ Expected output:
68
+
69
+ ```txt
70
+ { id: '...', status: 'ready' }
71
+ ```
72
+
73
+ ## 5. Run the Example
74
+
75
+ ```bash
76
+ cd examples
77
+ ABLO_API_KEY=sk_test_... npx tsx quickstart.ts
78
+ ```
79
+
80
+ ## 6. AI Activity on Existing State
81
+
82
+ Use `edit` when AI or background work will touch an existing row for more than a
83
+ quick write. Other participants can see the activity while your code runs. The
84
+ activity is cleared when `update` finishes; call `release` if the work ends
85
+ without a write.
86
+
87
+ ```ts
88
+ const edit = await ablo.weatherReports.edit('weather_stockholm', {
89
+ activity: 'checking_weather',
90
+ field: 'forecast',
91
+ ttl: '2m',
92
+ });
93
+
94
+ // Your existing weather tool or agent call. While this runs, other clients see
95
+ // that weather_stockholm is being checked.
96
+ const weather = await weatherAgent.getWeather(edit.current.location, {
97
+ signal: edit.signal,
98
+ });
99
+
100
+ await edit.update({
101
+ status: 'ready',
102
+ forecast: weather.summary,
103
+ });
104
+ ```
105
+
106
+ Ablo does not fetch the weather. It keeps the activity visible, gives the agent
107
+ call an abort signal if the row changes, and clears the activity when
108
+ `edit.update(...)` finishes.
109
+
110
+ ## 7. Multiplayer and Busy Work
111
+
112
+ There is no separate multiplayer mode. Use the same schema client for human UI,
113
+ server actions, and agents; Ablo fans out confirmed writes and keeps active
114
+ intents visible on the same resource.
115
+
116
+ Intents tell you when another human or agent is active on the same target. For
117
+ schema clients, wait on the intent stream and then write through the model.
118
+
119
+ ```ts
120
+ const busy = ablo.intents.list({
121
+ resource: 'weatherReports',
122
+ id: 'weather_stockholm',
123
+ });
124
+
125
+ if (busy.length > 0) {
126
+ await ablo.intents.waitFor(
127
+ { resource: 'weatherReports', id: 'weather_stockholm' },
128
+ { timeout: 30_000 },
129
+ );
130
+ }
131
+
132
+ await ablo.weatherReports.update('weather_stockholm', { status: 'ready' });
133
+ ```
134
+
135
+ `ifBusy` controls what happens when another human or agent is already working
136
+ on the same target:
137
+
138
+ - `return` returns immediately with active intents.
139
+ - `wait` waits for the intent stream to clear.
140
+ - `fail` throws `AbloBusyError` with the active intents attached.
141
+
142
+ ## 8. Next Steps
143
+
144
+ Keep using the schema client for app and agent writes. Reach for the advanced
145
+ schema-less agent wrapper only when a worker intentionally cannot import the
146
+ app schema.
147
+
148
+ - [Integration Guide](./integration-guide.md) explains the full app, React, Data Source, multiplayer, and agent path.
149
+ - [Guarantees](./guarantees.md) explains what confirmed writes and stale checks mean.
150
+ - [Client Behavior](./client-behavior.md) covers errors, retries, and public imports.
151
+ - [Connect Your Database](./data-sources.md) covers the optional route for teams keeping rows in their own database.
152
+ - [AI SDK Tool](./examples/ai-sdk-tool.md) shows the same write path inside a tool call.
package/docs/react.md ADDED
@@ -0,0 +1,115 @@
1
+ # React
2
+
3
+ The React bindings for `@ablo/sync-engine`. Use them when you want live
4
+ data on the client without writing fetch + WebSocket plumbing yourself.
5
+
6
+ For the full app structure, including server loads, existing backends, and
7
+ agents, start with [Integration Guide](/docs/integration-guide).
8
+
9
+ ## Installation
10
+
11
+ The React bindings ship with the main package — no extra install.
12
+
13
+ ```ts
14
+ import { useAblo } from '@ablo/sync-engine/react';
15
+ ```
16
+
17
+ ## useAblo — model resource
18
+
19
+ ```tsx
20
+ 'use client';
21
+
22
+ import { useAblo } from '@ablo/sync-engine/react';
23
+
24
+ export function TaskView({ task: serverTask }: { task: { id: string; title: string } }) {
25
+ const task = useAblo((ablo) => ablo.tasks.retrieve(serverTask.id)) ?? serverTask;
26
+ const intents = useAblo((ablo) =>
27
+ ablo.intents.list({ resource: 'tasks', id: serverTask.id }),
28
+ ) ?? [];
29
+ const busy = intents.length > 0;
30
+
31
+ return <article>{task.title}</article>;
32
+ }
33
+ ```
34
+
35
+ The hook:
36
+
37
+ 1. Reads through the same `ablo.<model>` methods as the rest of the SDK.
38
+ 2. Tracks the model fields read by the selector and re-renders when confirmed
39
+ deltas arrive.
40
+ 3. Lets Server Component data stay outside the hook: use `?? serverTask` when a
41
+ parent already loaded the row.
42
+ 4. Works for coordination state too, such as `ablo.intents.list(...)`.
43
+
44
+ Use the zero-argument form only when you need the full client for callbacks,
45
+ effects, or writes:
46
+
47
+ ```tsx
48
+ const abloClient = useAblo();
49
+ ```
50
+
51
+ Prefer selector reads like `useAblo((ablo) => ablo.<model>.retrieve(id))`.
52
+ String model names are kept on older hooks for compatibility, but first examples
53
+ should use the same model-resource shape as the rest of the SDK.
54
+
55
+ For collections, keep the selector on the model resource too:
56
+
57
+ ```tsx
58
+ const tasks = useAblo((ablo) =>
59
+ ablo.tasks.list({
60
+ where: { projectId },
61
+ filter: (task) => task.status !== 'done',
62
+ scope: 'live',
63
+ }),
64
+ );
65
+ ```
66
+
67
+ ## Server Load
68
+
69
+ ```tsx
70
+ const [task] = await ablo.tasks.load({ where: { id } });
71
+ ```
72
+
73
+ Use `load` in Server Components when the row may not be in the local pool yet.
74
+
75
+ ## Writes
76
+
77
+ For Server Actions and route handlers, call the SDK directly:
78
+
79
+ ```ts
80
+ import { ablo } from '@/lib/ablo';
81
+
82
+ const snap = ablo.snapshot({ tasks: id });
83
+ await ablo.tasks.update(id, patch, {
84
+ readAt: snap.stamp,
85
+ onStale: 'reject',
86
+ wait: 'confirmed',
87
+ });
88
+ ```
89
+
90
+ For client event handlers, get the provider-owned client and call the same
91
+ model resource:
92
+
93
+ ```tsx
94
+ const ablo = useAblo();
95
+
96
+ async function markDone() {
97
+ if (!ablo) return;
98
+ const snap = ablo.snapshot({ tasks: id });
99
+ await ablo.tasks.update(
100
+ id,
101
+ { status: 'done' },
102
+ { readAt: snap.stamp, onStale: 'reject', wait: 'confirmed' },
103
+ );
104
+ }
105
+ ```
106
+
107
+ The selector form is for render-time reads. The zero-argument form is for
108
+ imperative work after an event or effect.
109
+
110
+ See [API reference](/docs/api) for the full options surface.
111
+
112
+ ## Next.js
113
+
114
+ The Next.js [App Router landing](/nextjs) walks through Server Components
115
+ + Server Actions + `useAblo` together.
@@ -0,0 +1,45 @@
1
+ # Roadmap
2
+
3
+ What is shipped, what is next, and what we will not build.
4
+
5
+ ## Shipped
6
+
7
+ - **Resources, intents, commits** — the core API.
8
+ - **Capability tokens** — Biscuit-signed, scoped, attenuable, hot-revocable.
9
+ - **Audit log** — hash-chained per principal, with `delegationChainRoot`.
10
+ - **MCP transport** — HTTP server at `/api/mcp`.
11
+ - **TypeScript SDK** — `@ablo/sync-engine`, with React bindings.
12
+ - **Dashboard** — keys, audit, metrics, allowed origins.
13
+
14
+ ## In flight
15
+
16
+ - **Real-time presence** — see who else is viewing/editing a resource.
17
+ - **Cross-instance fan-out via Redis** — pub/sub deltas at scale.
18
+ - **Hot capability revocation UI** — in the dashboard, today via API only.
19
+
20
+ ## On deck
21
+
22
+ - **Schema migrations** — declarative resource schema changes.
23
+ - **Field-level subscriptions** — subscribe to one path, not the whole row.
24
+ - **Bulk import/export** — CSV/JSON round-trip with chain verification.
25
+
26
+ ## Maybe, if demand
27
+
28
+ - **Python SDK** — when a customer is shipping a Python-only product.
29
+ - **Go SDK** — same.
30
+ - **Multi-region replication** — when latency requirements force it.
31
+
32
+ ## We will not build
33
+
34
+ - **A general-purpose Postgres wrapper** — Ablo Sync is for state with
35
+ concurrency semantics, not for storing every table.
36
+ - **Server-side compute** — no triggers, no stored procedures. Compute
37
+ belongs in your application code.
38
+ - **A document database UI** — your data lives in Ablo Sync; the UI is your
39
+ product, not ours.
40
+
41
+ ## How priorities shift
42
+
43
+ We move items between sections based on what customers ask for in
44
+ production. Filing a [feature request](/docs/contact) with a concrete use
45
+ case is more effective than a thread on Twitter.
@@ -0,0 +1,54 @@
1
+ # @ablo/sync-engine Examples
2
+
3
+ The examples teach the same path as the README and docs: declare a schema,
4
+ create or load typed models, and write through `ablo.<model>`.
5
+
6
+ ```ts
7
+ import Ablo from '@ablo/sync-engine';
8
+ import { defineSchema, model, z } from '@ablo/sync-engine/schema';
9
+
10
+ const schema = defineSchema({
11
+ weatherReports: model({
12
+ location: z.string(),
13
+ status: z.enum(['pending', 'ready']),
14
+ forecast: z.string().optional(),
15
+ }),
16
+ });
17
+
18
+ const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
19
+ ```
20
+
21
+ Then:
22
+
23
+ - create with `ablo.weatherReports.create`
24
+ - read with `ablo.weatherReports.load`
25
+ - mark long-running AI work with `ablo.weatherReports.edit`
26
+ - write with `ablo.weatherReports.update`
27
+ - wait for confirmation when the write must be durable before continuing
28
+
29
+ Use schema-less resources and `commits.create` only for advanced runtimes that
30
+ intentionally cannot import the app schema.
31
+
32
+ ## Running
33
+
34
+ ```bash
35
+ cd packages/sync-engine/examples
36
+ ABLO_API_KEY=sk_test_... npx tsx quickstart.ts
37
+ ```
38
+
39
+ ## Data Source (customer-owned database)
40
+
41
+ `data-source/` is a self-contained, runnable end-to-end demo of the
42
+ HTTP contract Ablo Cloud uses to talk to a customer's database. It
43
+ needs no API key, no cloud connection, and no open ports: the
44
+ orchestrator drives the customer handler in-process so signer and
45
+ verifier exchange real signed bytes without leaving the process.
46
+
47
+ ```bash
48
+ cd packages/sync-engine/examples
49
+ npx tsx data-source/run.ts
50
+ ```
51
+
52
+ See `data-source/README.md` for what each file teaches and the
53
+ production wiring snippets (Next.js, Hono, Cloudflare Workers,
54
+ plain Node).
@@ -0,0 +1,102 @@
1
+ # Data Source Example
2
+
3
+ End-to-end demo of the Data Source contract — the path customers take
4
+ when they want Ablo to coordinate writes against rows stored in
5
+ **their** database.
6
+
7
+ ## What's in here
8
+
9
+ | File | Side | Purpose |
10
+ | --------------------- | ------------------ | ------------------------------------------------------------- |
11
+ | `schema.ts` | Shared | The Zod schema both Ablo and the customer compile against |
12
+ | `customer-server.ts` | Customer | The `dataSource(...)` handler — copy as a skeleton |
13
+ | `ablo-driver.ts` | Ablo Cloud | Signs requests the way Ablo Cloud does in production |
14
+ | `run.ts` | Orchestrator | Drives load -> commit -> list -> events round-trip |
15
+
16
+ ## Run
17
+
18
+ ```bash
19
+ cd packages/sync-engine/examples
20
+ npx tsx data-source/run.ts
21
+ ```
22
+
23
+ No network port, no env vars, no cloud credentials. The orchestrator
24
+ calls the handler in-process. Signer and verifier still exchange
25
+ signed bytes — flip the secret and you'll see a 401.
26
+
27
+ ## What it proves
28
+
29
+ 1. **Signer/verifier interop.** Ablo Cloud's
30
+ `signAbloSourceRequest` and the customer's `dataSource(...)`
31
+ speak the same wire format (Standard Webhooks v1).
32
+ 2. **All four request types** — `load`, `list`, `commit`, `events`
33
+ — share one handler.
34
+ 3. **The customer DB stays canonical.** Ablo never sees rows
35
+ directly; it only sees the response payload from the customer's
36
+ handler.
37
+ 4. **The outbox feed.** Writes that bypass Ablo (cron, dashboards,
38
+ batch imports) show up on the next `events` poll so Ablo can fan
39
+ them out to connected clients.
40
+
41
+ ## Production wiring
42
+
43
+ The customer's handler is a Fetch-API function:
44
+ `(req: Request) => Promise<Response>`. Drop it anywhere that speaks
45
+ that contract.
46
+
47
+ ### Next.js App Router
48
+
49
+ ```ts
50
+ // app/api/ablo/source/route.ts
51
+ export { handleAbloSource as POST } from '@/lib/ablo-source';
52
+ ```
53
+
54
+ ### Hono / Cloudflare Workers
55
+
56
+ ```ts
57
+ import { Hono } from 'hono';
58
+ import { handleAbloSource } from './ablo-source';
59
+
60
+ const app = new Hono();
61
+ app.post('/api/ablo/source', (c) => handleAbloSource(c.req.raw));
62
+ ```
63
+
64
+ ### Node `http` server
65
+
66
+ ```ts
67
+ import { createServer } from 'node:http';
68
+ import { handleAbloSource } from './ablo-source';
69
+
70
+ createServer(async (req, res) => {
71
+ const body = await new Promise<string>((resolve) => {
72
+ let chunks = '';
73
+ req.on('data', (c) => (chunks += c));
74
+ req.on('end', () => resolve(chunks));
75
+ });
76
+ const request = new Request(`http://localhost${req.url}`, {
77
+ method: req.method,
78
+ headers: req.headers as Record<string, string>,
79
+ body,
80
+ });
81
+ const response = await handleAbloSource(request);
82
+ res.writeHead(response.status, Object.fromEntries(response.headers));
83
+ res.end(await response.text());
84
+ }).listen(3000);
85
+ ```
86
+
87
+ ## Migration checklist for an existing backend
88
+
89
+ Replace the `Map`-based store in `customer-server.ts` with your real
90
+ data layer. The handler shape stays the same:
91
+
92
+ - `tasks.load({ id })` -> `db.task.findUnique({ where: { id } })`
93
+ - `tasks.list({ query })` -> `db.task.findMany({ take, cursor })`
94
+ - `tasks.commit({ operations, clientTxId })` -> `db.$transaction` that
95
+ applies each `op` and tags the row with `clientTxId` for idempotent
96
+ retries
97
+ - `events({ cursor, limit })` -> read from your outbox table, return
98
+ rows with their `clientTxId` (Ablo dedupes its own commits) and the
99
+ resume cursor
100
+
101
+ See `docs/examples/existing-python-backend.md` for the same pattern
102
+ expressed against a Python backend.