@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.
- package/CHANGELOG.md +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- 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.
|
package/docs/roadmap.md
ADDED
|
@@ -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.
|