@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
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).