@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Schema-driven identity sync-group composition, plus a terser capability surface.
|
|
6
|
+
|
|
7
|
+
The convention for deriving a participant's allowed sync-groups from its identity is now declared on the consumer's schema as an open registration. Consumers with a `{ regionId, customerId }` identity shape declare their own roles instead of receiving any built-in prefixes from the SDK.
|
|
8
|
+
|
|
9
|
+
Capability fields shed their redundant `allowed` prefix to match the surrounding vocabulary — capability inputs always describe what the bearer *can* touch, so the prefix was doing no disambiguation work for the consumer.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `DefineSchemaOptions.identityRoles?: readonly IdentityRole[]` — open registration of identity-anchored sync-group roles on `defineSchema(...)`. Each `IdentityRole` declares `{ kind, template, extract }`: a diagnostic label, a `'<prefix>:{id}'` template, and a pure extractor function from an opaque identity context to zero-or-more ids. No closed enum; consumers fully control both the template strings and the extraction logic.
|
|
14
|
+
- `composeIdentitySyncGroups(identity, schema)` exported from `@ablo/sync-engine/schema` — walks the schema's registered `identityRoles`, calls each extractor, and substitutes ids into templates. Stable, deduped output. Returns `[]` when no roles are registered.
|
|
15
|
+
- `Schema.identityRoles: readonly IdentityRole[]` — the registered list, accessible on every `defineSchema(...)` result.
|
|
16
|
+
- New exported types: `IdentityRole`, `IdentityContext`.
|
|
17
|
+
|
|
18
|
+
### Breaking
|
|
19
|
+
|
|
20
|
+
- `capabilities.create({ allowedSyncGroups, allowedOperations })` → `capabilities.create({ syncGroups, operations })`. Both fields renamed at every public surface — capability create input, capability retrieve response, capability record, Identity returned from `AuthProvider`. Hard rename, no alias. Update the call sites; the field semantics are unchanged.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Before
|
|
24
|
+
await api.capabilities.create({
|
|
25
|
+
allowedSyncGroups: ['org:acme'],
|
|
26
|
+
allowedOperations: ['tasks.update'],
|
|
27
|
+
lease: '10m',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// After
|
|
31
|
+
await api.capabilities.create({
|
|
32
|
+
syncGroups: ['org:acme'],
|
|
33
|
+
operations: ['tasks.update'],
|
|
34
|
+
lease: '10m',
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- `docs/integration-guide.md` §1 now shows `identityRoles` in the canonical `defineSchema` example plus a "Declaring scope on a model" subsection covering `orgScoped` / `scopedVia` / `syncGroupFormat`. `docs/capabilities.md`, `docs/api.md`, `docs/mcp.md`, and `AGENTS.md` cross-reference the `identityRoles` section and use the renamed fields throughout.
|
|
41
|
+
|
|
42
|
+
## 0.3.0 (2026-04-22)
|
|
43
|
+
|
|
44
|
+
Umbrella `<AbloProvider>` for React apps. One provider component now owns the full lifecycle — singleton rotation on auth change, Strict-Mode-safe bootstrap, `beforeunload` cleanup, session-expiry IndexedDB wipe, post-bootstrap hooks, mesh client construction. Replaces the ad-hoc provider glue every consumer had to write themselves.
|
|
45
|
+
|
|
46
|
+
Declarative props absorb every class of lifecycle glue; the status hook returns a tagged union so impossible states are unrepresentable. The reference integration shrank from 515 LOC of hand-rolled singleton/AbortController/beforeunload/reaction-bridge wiring to a 60-LOC thin wrapper that just passes props through.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- `<AbloProvider>` — umbrella provider at `@ablo/sync-engine/react`. Props include data config (`schema`, `url`, `userId`, `organizationId`), auth (`capabilityToken` / `apiKey` / session cookie fallback), declarative behavior (`preventUnsavedChanges`, `lostConnectionTimeout`, `postBootstrap`), callbacks (`onSessionExpired`, `onError`, `resolveUsers`), and DI escape hatches.
|
|
51
|
+
- `<SyncGroupProvider id="matter:...">` + `useSyncGroup()` — per-entity scope context.
|
|
52
|
+
- `<ClientSideSuspense fallback={...}>` — gate renders until the engine reports `connected`. Phase-1 non-Suspense; phase-2 upgrades to real Suspense.
|
|
53
|
+
- `useSyncStatus()` rewritten as a tagged union: `{ name: 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'needs-auth', ... }`. Impossible states are unrepresentable.
|
|
54
|
+
- `useCurrentUserId()` — returns the `userId` prop. Replaces downstream consumers' defineProperty hacks on the store.
|
|
55
|
+
- `useErrorListener(cb)` — imperative error callback (Sentry/Datadog).
|
|
56
|
+
- `useSync<R>()` and `useSyncStore<T>()` accept generic parameters so consumers can widen to their concrete schema types without `as unknown` casts at call sites.
|
|
57
|
+
- `BaseSyncedStore.purge()` / `SyncEngine.purge()` — disconnect + wipe every `ablo_*` / `ablo-*` IndexedDB. Called automatically on session expiry.
|
|
58
|
+
- `SyncEngine.onSessionError(listener)` — subscribe to session-error events. Multiple subscribers supported.
|
|
59
|
+
- Commit payload projection built into `TransactionQueue`. Mutations are automatically projected onto the model's schema-declared fields (dropping framework internals `__class` / `__typename` / `clientId` / `syncStatus` and anything not declared), with `field.json()` values auto-stringified for TEXT columns and `undefined` dropped on updates. No config port, no consumer hook — the SDK derives correct wire payloads from the schema alone. Apps that previously maintained hand-rolled extractor tables can delete them entirely.
|
|
60
|
+
|
|
61
|
+
### Breaking (continued)
|
|
62
|
+
|
|
63
|
+
- Removed `SyncEngineConfig.extractCreateInput` and `SyncEngineConfig.buildUpdateInput`. The SDK's built-in projection replaces them. Consumers who passed these in `configOverrides` should delete the override; the default now covers 100% of identity-column mutations. The `configOverrides` prop still exists but its remaining fields are all deprecated (see below) and scheduled for removal in v0.4.
|
|
64
|
+
|
|
65
|
+
### Deprecated (vestigial — removal in v0.4)
|
|
66
|
+
|
|
67
|
+
- `SyncEngineConfig.modelCreatePriority`, `defaultCreatePriority`, `defaultNonCreatePriority` — never read at runtime.
|
|
68
|
+
- `SyncEngineConfig.batchableModels` — never read at runtime.
|
|
69
|
+
- `SyncEngineConfig.dedicatedDeleteModels` — never read at runtime.
|
|
70
|
+
- `SyncEngineConfig.preserveCaseModels` — never read at runtime.
|
|
71
|
+
- `SyncEngineConfig.essentialFields` — used only in debug logging, no behavioral effect.
|
|
72
|
+
- `SyncEngineConfig.classNameFallbackMap` — dead path; `ModelRegistry.registerModelsFromSchema` registers by constructor identity, bypassing the class-name fallback entirely.
|
|
73
|
+
|
|
74
|
+
### Breaking
|
|
75
|
+
|
|
76
|
+
- Removed `<SyncProvider>` — folded into `<AbloProvider>`. Migrate by swapping the provider and passing `userId`/`organizationId`/`url` instead of a pre-constructed store.
|
|
77
|
+
- Removed `createAbloContext()` factory and its returned `AbloProvider` / `useAblo` / `useParticipant` triple. Mesh is now always-on inside `<AbloProvider>`; `useAblo()` and `useParticipant(opts)` are always available. Schema-typed mesh hooks are on the roadmap.
|
|
78
|
+
- Removed `withSync` (no-op alias of `observer`). Import `observer` from `mobx-react-lite` directly if needed.
|
|
79
|
+
- Removed `useSyncContext` from the public surface (never used outside the SDK's test helpers).
|
|
80
|
+
- `useSyncStatus()` return shape changed from six booleans to a tagged union. Migration: `const { isReady } = useSyncStatus()` → `const status = useSyncStatus(); const isReady = status.name === 'connected'`.
|
|
81
|
+
- `SyncStoreContract` gained six sync-status getters and a `syncStatus` field. Third-party classes implementing the contract must add these (additive for callers).
|
|
82
|
+
|
|
83
|
+
### Migration
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Before (0.2.x)
|
|
87
|
+
const { AbloProvider, useAblo, useParticipant } = createAbloContext<typeof schema>();
|
|
88
|
+
|
|
89
|
+
function Root() {
|
|
90
|
+
const sync = createSyncEngine({ url, schema, user });
|
|
91
|
+
const ablo = new Ablo({ schema });
|
|
92
|
+
return (
|
|
93
|
+
<SyncProvider store={sync._store} organizationId={orgId}>
|
|
94
|
+
<AbloProvider ablo={ablo}>
|
|
95
|
+
<App />
|
|
96
|
+
</AbloProvider>
|
|
97
|
+
</SyncProvider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// After (0.3.0)
|
|
102
|
+
function Root() {
|
|
103
|
+
return (
|
|
104
|
+
<AbloProvider
|
|
105
|
+
schema={schema}
|
|
106
|
+
url={url}
|
|
107
|
+
userId={userId}
|
|
108
|
+
organizationId={orgId}
|
|
109
|
+
preventUnsavedChanges
|
|
110
|
+
onSessionExpired={() => router.replace('/signin')}
|
|
111
|
+
>
|
|
112
|
+
<ClientSideSuspense fallback={<Skeleton />}>
|
|
113
|
+
<App />
|
|
114
|
+
</ClientSideSuspense>
|
|
115
|
+
</AbloProvider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
No breaking change to `useQuery` / `useOne` / `useMutate` / `useReader` / `useMutators` / `useUndoScope` / `usePresence` / `useIntent` — call sites remain source-compatible.
|
|
121
|
+
|
|
122
|
+
## 0.2.1 (2026-04-22)
|
|
123
|
+
|
|
124
|
+
React bindings hardening. Fixes two infinite-loop classes that surfaced in downstream apps as React error #185 ("Maximum update depth exceeded"), and exposes sync-status reactivity as a first-class observable + hook.
|
|
125
|
+
|
|
126
|
+
### Fixed
|
|
127
|
+
|
|
128
|
+
- **`useQuery` / `useOne` no longer loop on `getSnapshot`.** The `useSyncExternalStore` adapter was returning a fresh `view.results.slice()` on every call, which React's post-commit consistency check interpreted as "store updated mid-render" — scheduling another render, another snapshot, another mismatch, ad infinitum. The snapshot is now cached in a ref and only refreshed inside the subscribe callback right before `onChange()` fires. Affected every tree with multiple simultaneous `useQuery` subscribers.
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **`BaseSyncedStore` sync status is now properly observable.** `syncStatus` and `dataReady` are annotated `observable`; `isReady`, `isSyncing`, `isOffline`, `isReconnecting`, `isError`, `hasUnsyncedChanges` are `computed`. Before, these were plain getters over plain fields — `reaction(() => store.isReady, ...)` silently never fired. Existing `observer` / `reaction` call sites that relied on the implicit `pool.size` trigger will continue to work; new call sites should read these observables directly.
|
|
133
|
+
- **`useSyncStatus()` React hook.** Returns `{ isReady, isSyncing, isOffline, isReconnecting, isError, hasUnsyncedChanges }` as a reactive snapshot, bridged via `useSyncExternalStore` with a correctly-cached snapshot. Replaces hand-rolled `reaction` bridges in consumer providers. See `docs/react.md`.
|
|
134
|
+
- **`SyncStoreContract` surfaces the status getters** so TypeScript autocomplete works from the `useSyncContext()` return value without a cast.
|
|
135
|
+
|
|
136
|
+
### Documentation
|
|
137
|
+
|
|
138
|
+
- **`llms.txt` and `docs/react.md`** gained a "Common pitfalls" section covering the three traps this release addresses: don't wrap providers in `observer()`, `getSnapshot` must return a cached reference, and sync-status fields are real observables (don't watch `pool.size` as a proxy).
|
|
139
|
+
|
|
140
|
+
### Migration
|
|
141
|
+
|
|
142
|
+
No breaking changes. Optional: replace any local `reaction(() => store.isReady, setReady, { fireImmediately: true })` bridges in your own providers with `const { isReady } = useSyncStatus()` for consumers below the store provider.
|
|
143
|
+
|
|
144
|
+
## 0.2.0 (2026-04-21)
|
|
145
|
+
|
|
146
|
+
Mesh SDK — the canonical agent-multiplayer surface. Locked at this release; further work is consolidation, not expansion.
|
|
147
|
+
|
|
148
|
+
### What's frozen
|
|
149
|
+
|
|
150
|
+
The SDK covers exactly three integration shapes. Each has a canonical example in [`examples/`](./examples/):
|
|
151
|
+
|
|
152
|
+
1. **Server agent** — `new Ablo({ schema })` reads `ABLO_API_KEY`, joins and works. ([`examples/server-agent.ts`](./examples/server-agent.ts))
|
|
153
|
+
2. **Browser app** — server mints a scoped capability, browser holds it via `new Ablo({ schema, capabilityToken })`. No API key in bundle, no session cookies, no allowed-origins registration required. Stripe `client_secret` shape. ([`examples/browser-app.ts`](./examples/browser-app.ts))
|
|
154
|
+
3. **Sub-agent** — `parent.join(child, opts)` attenuates from the parent's capability. ([`examples/sub-agent.ts`](./examples/sub-agent.ts))
|
|
155
|
+
|
|
156
|
+
### Ergonomics (package-wide)
|
|
157
|
+
|
|
158
|
+
- **`Ablo` class** — `import Ablo from '@ablo/sync-engine'` / `new Ablo({ schema })`. Matches `new Stripe()` / `new OpenAI()` / `new Anthropic()` pattern. `createMesh(opts)` stays available as the functional alias.
|
|
159
|
+
- **Model-scoped joins** — `ablo.matters.join(id, { label })` desugars to the generic `join`. Proxy-based so the namespace adapts to any schema. Collisions with reserved admin fields (`roles`, `members`, `audit`, `capabilities`) throw at construction time.
|
|
160
|
+
- **Flat scope form** — `scope: { matters: id }` alongside the array form.
|
|
161
|
+
- **`as` alias** — `{ as: session({...}) }` replaces the security-jargon `onBehalfOf`; both still accepted.
|
|
162
|
+
- **Auto-connect** — `join()` returns a connected participant. `autoConnect: false` to opt out.
|
|
163
|
+
- **Duration strings** — `ttl: '3m'`, `ttlSeconds: '24h'` accepted alongside numbers.
|
|
164
|
+
- **Descriptive generics** — every public type uses `TSchema` / `TAgent` / `ModelName` instead of `S` / `A` / `K`. Zero `unknown` in public types.
|
|
165
|
+
|
|
166
|
+
### Coordination primitives
|
|
167
|
+
|
|
168
|
+
- **Presence verbs** — `participant.presence.editing(target)` / `viewing(target)` / `idle()`. Plus `update({...})` escape hatch for custom actions.
|
|
169
|
+
- **Intent verbs** — `participant.intents.editing(target, opts)` / `writing(target, opts)`. Returns an `IntentHandle` with `Symbol.asyncDispose` so `await using work = ...` auto-revokes.
|
|
170
|
+
- **Snapshots** — `const snap = await participant.snapshot({ clauses: [id] })`. Flat shape: `snap.clauses[id]` (typed from schema via `InferModel`, not `unknown`), `snap.stamp`, `snap.signal` (AbortSignal).
|
|
171
|
+
- **Async iterables** — `for await (const peers of participant.presence)`, `for await (const openIntents of participant.intents)`, `for await (const delta of participant.deltas)`.
|
|
172
|
+
|
|
173
|
+
### Env / config
|
|
174
|
+
|
|
175
|
+
- `ABLO_API_KEY` — required for server-side use.
|
|
176
|
+
- `ABLO_BASE_URL` — optional override for staging / local-dev (defaults to `https://mesh.ablo.finance`). Not a customer-facing self-hosting path.
|
|
177
|
+
- `organizationId` — **no longer required** in `createMesh`. The API key or session binds the caller to one org; the capability mint response echoes it back.
|
|
178
|
+
- `createMeshFromEnv` — removed. `new Ablo({ schema })` auto-reads env.
|
|
179
|
+
|
|
180
|
+
### Test coverage
|
|
181
|
+
|
|
182
|
+
- 53 mesh unit tests across 8 suites (`__tests__/unit/mesh/`)
|
|
183
|
+
- New E2E test `e2e-browser-capability-token.ts` proves the server-mints / browser-holds flow end-to-end
|
|
184
|
+
- Existing 12 mesh E2E tests (token refresh, watermark, chinese wall, etc.) still pass
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 0.1.0 (2026-04-10)
|
|
189
|
+
|
|
190
|
+
Initial release.
|
|
191
|
+
|
|
192
|
+
### Features
|
|
193
|
+
|
|
194
|
+
- **Schema DSL**: Zero-codegen schema definition with full TypeScript inference (`defineSchema`, `field`, `relation`)
|
|
195
|
+
- **React Hooks**: `useModels`, `useModel`, `useMutations`, `withSync` for reactive data binding
|
|
196
|
+
- **Consumer API**: `createSyncEngine()` — one-liner setup that hides all internal wiring
|
|
197
|
+
- **Offline-first**: IndexedDB persistence with automatic offline mutation queue and FK-safe flush
|
|
198
|
+
- **Real-time sync**: WebSocket delta streaming with optimistic updates and rollback
|
|
199
|
+
- **AI Agent SDK**: `SyncAgent` for backend/AI agent participation as first-class sync citizens
|
|
200
|
+
- **Pluggable auth**: `AuthProvider` interface with built-in API key, JWT, and session providers
|
|
201
|
+
- **Security**: IndexedDB cleanup on session expiry and sync group revocation
|
|
202
|
+
- **Testing utilities**: `@ablo/sync-engine/testing` subpath with mocks, fixtures, and harness
|
|
203
|
+
|
|
204
|
+
### Test Coverage
|
|
205
|
+
|
|
206
|
+
- 231 unit/integration/property/contract tests
|
|
207
|
+
- 50 E2E tests against real Go server + PostgreSQL + Redis
|
|
208
|
+
- Property-based testing via fast-check
|
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for describing the origin of the Work and
|
|
141
|
+
reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Support. While redistributing the Work or
|
|
166
|
+
Derivative Works thereof, You may choose to offer, and charge a
|
|
167
|
+
fee for, acceptance of support, warranty, indemnity, or other
|
|
168
|
+
liability obligations and/or rights consistent with this License.
|
|
169
|
+
However, in accepting such obligations, You may act only on Your
|
|
170
|
+
own behalf and on Your sole responsibility, not on behalf of any
|
|
171
|
+
other Contributor, and only if You agree to indemnify, defend,
|
|
172
|
+
and hold each Contributor harmless for any liability incurred by,
|
|
173
|
+
or claims asserted against, such Contributor by reason of your
|
|
174
|
+
accepting any such warranty or support.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2025-2026 Fablo Innovation AB
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
200
|
+
implied. See the License for the specific language governing
|
|
201
|
+
permissions and limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@ablo/sync-engine
|
|
2
|
+
Copyright 2025-2026 Fablo Innovation AB
|
|
3
|
+
|
|
4
|
+
This product includes software developed by Fablo Innovation AB
|
|
5
|
+
(https://ablo.finance).
|
|
6
|
+
|
|
7
|
+
"Ablo" is a trademark of Fablo Innovation AB. This license does not grant
|
|
8
|
+
permission to use the Ablo name, logo, or trademarks. Third parties
|
|
9
|
+
may describe their use of or compatibility with Ablo factually (e.g.,
|
|
10
|
+
"built with @ablo/sync-engine") but may not use the Ablo name in a way
|
|
11
|
+
that suggests endorsement, affiliation, or origin without written
|
|
12
|
+
permission.
|
package/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Ablo
|
|
2
|
+
|
|
3
|
+
Ablo Sync is a schema-first state control layer for AI agents and collaborative apps.
|
|
4
|
+
|
|
5
|
+
Use it when human UI, server actions, and AI agents need to edit the same typed
|
|
6
|
+
state with realtime fanout, stale-write protection, active-work coordination,
|
|
7
|
+
and audit.
|
|
8
|
+
|
|
9
|
+
```txt
|
|
10
|
+
schema -> ablo.<model>.create/load/edit/update(...)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @ablo/sync-engine
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node 22+ and TypeScript 5+.
|
|
20
|
+
|
|
21
|
+
## Get a Test Key
|
|
22
|
+
|
|
23
|
+
Create an Ablo sandbox and copy an `sk_test_*` API key. Keep API keys in trusted
|
|
24
|
+
server runtimes only.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export ABLO_API_KEY=sk_test_...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Browser apps should use a scoped capability/session route through the React
|
|
31
|
+
provider. Do not ship `ABLO_API_KEY` in a browser bundle.
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import Ablo from '@ablo/sync-engine';
|
|
37
|
+
import { defineSchema, model, z } from '@ablo/sync-engine/schema';
|
|
38
|
+
|
|
39
|
+
const schema = defineSchema({
|
|
40
|
+
weatherReports: model({
|
|
41
|
+
location: z.string(),
|
|
42
|
+
status: z.enum(['pending', 'ready']),
|
|
43
|
+
forecast: z.string().optional(),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const ablo = Ablo({
|
|
48
|
+
schema,
|
|
49
|
+
apiKey: process.env.ABLO_API_KEY,
|
|
50
|
+
});
|
|
51
|
+
|
|
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
|
+
await ablo.dispose();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Expected output:
|
|
70
|
+
|
|
71
|
+
```txt
|
|
72
|
+
{ id: '...', status: 'ready' }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Pass `schema` for typed model resources. Omit it only for advanced server-side
|
|
76
|
+
resource clients such as custom agents and MCP routes.
|
|
77
|
+
|
|
78
|
+
Run the package example from this directory:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd examples
|
|
82
|
+
ABLO_API_KEY=sk_test_... npx tsx quickstart.ts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a production integration with React, an existing backend, Data Source, and
|
|
86
|
+
future agents, read [Integration Guide](./docs/integration-guide.md).
|
|
87
|
+
|
|
88
|
+
## AI Activity on Existing State
|
|
89
|
+
|
|
90
|
+
Use `edit` when AI or background work will touch an existing row for more than a
|
|
91
|
+
quick write. Other participants can see the activity while your code runs. The
|
|
92
|
+
activity is cleared when `update` finishes; call `release` if the work ends
|
|
93
|
+
without a write.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const edit = await ablo.weatherReports.edit('weather_stockholm', {
|
|
97
|
+
activity: 'checking_weather',
|
|
98
|
+
field: 'forecast',
|
|
99
|
+
ttl: '2m',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Your existing weather tool or agent call. While this runs, other clients see
|
|
103
|
+
// that weather_stockholm is being checked.
|
|
104
|
+
const weather = await weatherAgent.getWeather(edit.current.location, {
|
|
105
|
+
signal: edit.signal,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await edit.update({
|
|
109
|
+
status: 'ready',
|
|
110
|
+
forecast: weather.summary,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Ablo does not fetch the weather. It keeps the activity visible, gives the agent
|
|
115
|
+
call an abort signal if the row changes, and clears the activity when
|
|
116
|
+
`edit.update(...)` finishes.
|
|
117
|
+
|
|
118
|
+
## Multiplayer
|
|
119
|
+
|
|
120
|
+
There is no separate multiplayer mode. When human UI, server actions, and agent
|
|
121
|
+
workers use the same schema client and write through `ablo.<model>`, they are on
|
|
122
|
+
the same shared resource stream.
|
|
123
|
+
|
|
124
|
+
- `ablo.<model>.create/update/delete` fan out confirmed deltas to subscribers.
|
|
125
|
+
- `useAblo(...)` gives React clients the live row plus active intents.
|
|
126
|
+
- `ablo.<model>.edit(...)` lets humans and agents see active work before a write lands.
|
|
127
|
+
- `ablo.intents` remains available for custom lower-level coordination.
|
|
128
|
+
|
|
129
|
+
If a team writes directly to its own database outside Ablo, that write bypasses
|
|
130
|
+
the multiplayer stream until the app reports it through Data Source events.
|
|
131
|
+
|
|
132
|
+
Under the hood, capabilities, tasks, leases, intents, commits, and receipts are
|
|
133
|
+
real protocol primitives. They exist so agent work is scoped, coordinated,
|
|
134
|
+
attributable, and cleaned up if a runtime disappears. They should not be
|
|
135
|
+
ceremony in the first integration.
|
|
136
|
+
|
|
137
|
+
## Load vs Retrieve
|
|
138
|
+
|
|
139
|
+
For schema clients, `load` and `retrieve` are intentionally different:
|
|
140
|
+
|
|
141
|
+
- `ablo.weatherReports.load({ where })` is async. It hydrates matching rows from the
|
|
142
|
+
local store and server, then returns them.
|
|
143
|
+
- `ablo.weatherReports.retrieve(id)` is sync. It reads one already-loaded row from the
|
|
144
|
+
local pool and returns `undefined` if it is not loaded yet.
|
|
145
|
+
- `ablo.resource('weatherReports').retrieve(id)` is the lower-level resource API. It
|
|
146
|
+
returns `{ data, stamp, intents }` for custom runtimes that need raw read
|
|
147
|
+
stamps and receipts.
|
|
148
|
+
|
|
149
|
+
## Activity and Busy State
|
|
150
|
+
|
|
151
|
+
Model edit activity is the live coordination signal. If another participant is
|
|
152
|
+
reading, editing, or updating an entity, Ablo can return that state, wait for it
|
|
153
|
+
to clear, or fail fast with `AbloBusyError`.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const busy = ablo.intents.list({
|
|
157
|
+
resource: 'weatherReports',
|
|
158
|
+
id: 'weather_stockholm',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (busy.length > 0) {
|
|
162
|
+
console.log(`${busy[0].actor} is ${busy[0].action}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await ablo.intents.waitFor(
|
|
166
|
+
{ resource: 'weatherReports', id: 'weather_stockholm' },
|
|
167
|
+
);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Policy names are literal:
|
|
171
|
+
|
|
172
|
+
- `ifBusy: 'return'` returns immediately with `intents`.
|
|
173
|
+
- `ifBusy: 'wait'` waits on the live intent stream. Plain HTTP callers must
|
|
174
|
+
provide their own explicit polling policy instead of getting hidden SDK polling.
|
|
175
|
+
- `ifBusy: 'fail'` throws `AbloBusyError` with the active intents attached.
|
|
176
|
+
|
|
177
|
+
## Persistence
|
|
178
|
+
|
|
179
|
+
Ablo defaults to volatile local persistence. That keeps the SDK focused on shared
|
|
180
|
+
state coordination instead of silently adding an IndexedDB storage product to
|
|
181
|
+
every browser app.
|
|
182
|
+
|
|
183
|
+
Opt into browser durable local cache and offline queueing when you need it:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const ablo = Ablo({
|
|
187
|
+
schema,
|
|
188
|
+
apiKey: process.env.ABLO_API_KEY,
|
|
189
|
+
persistence: 'indexeddb',
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Node, SSR, tests, and agents use volatile in-memory persistence automatically.
|
|
194
|
+
|
|
195
|
+
## Connect Your Database
|
|
196
|
+
|
|
197
|
+
Every schema model has a backing store. By default, Ablo stores rows for the
|
|
198
|
+
models you declare, so `ablo.weatherReports.create(...)` and `ablo.weatherReports.update(...)`
|
|
199
|
+
write to Ablo-managed state.
|
|
200
|
+
|
|
201
|
+
If your existing database remains the source of truth, connect it with a signed
|
|
202
|
+
Data Source endpoint. Your app keeps the database credentials; Ablo sends signed
|
|
203
|
+
commit requests to your route.
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
ABLO_DATA_SOURCE_SIGNING_SECRET=whsec_...
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
See [Connect Your Database](./docs/data-sources.md) for the route and commit shape.
|
|
210
|
+
|
|
211
|
+
## Agent Runs
|
|
212
|
+
|
|
213
|
+
Most agent workers should import the same schema and use
|
|
214
|
+
`ablo.<model>.load(...)` plus `ablo.<model>.update(...)`. The schema-less
|
|
215
|
+
`agent.run(...)` wrapper exists for advanced workers that intentionally cannot
|
|
216
|
+
import the app schema.
|
|
217
|
+
|
|
218
|
+
## Production Reference
|
|
219
|
+
|
|
220
|
+
- [Guarantees](./docs/guarantees.md) — confirmed writes, stale-write protection, intent coordination, and agent lifecycle.
|
|
221
|
+
- [Integration Guide](./docs/integration-guide.md) — pick the backing mode and integrate React, Data Source, multiplayer, and agents.
|
|
222
|
+
- [Client Behavior](./docs/client-behavior.md) — options, errors, retries, timeouts, and public imports.
|
|
223
|
+
- [Connect Your Database](./docs/data-sources.md) — keep canonical rows in your app database without giving Ablo database credentials.
|
|
224
|
+
- [Existing Python Backend](./docs/examples/existing-python-backend.md) — migrate existing Python endpoints to multiplayer and agent-safe writes gradually.
|
|
225
|
+
- [AI SDK Tool](./docs/examples/ai-sdk-tool.md) — use Ablo inside an AI SDK tool call.
|
|
226
|
+
- [Server Agent](./docs/examples/server-agent.md) — schema-backed worker plus advanced schema-less run.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
Apache License 2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|