@abloatai/ablo 0.63.1 → 0.64.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 +48 -0
- package/dist/react.d.ts +2 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/docs/examples/account-multiplayer.md +2 -2
- package/docs/groups.md +4 -4
- package/docs/identity.md +1 -1
- package/docs/react.md +87 -24
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.64.0
|
|
4
|
+
|
|
5
|
+
React reads the same core objects as ordinary Ablo code. Read connection state with `ablo.status` outside React and `useAblo(ablo => ablo.status)` inside React; its type is `Ablo.Status`. Presence reads through `useAblo(ablo => ablo.presence.others)` or `ablo.presence.forModel(model, id)` now react to activity changes. The unregistered-schema selector also preserves core properties and methods.
|
|
6
|
+
|
|
7
|
+
This is a breaking API cleanup for 0.64, with no compatibility aliases:
|
|
8
|
+
|
|
9
|
+
- Replace `useSyncStatus()` with `useAblo(ablo => ablo.status)`, and `SyncStatusSnapshot` with `Ablo.Status`. Replace the client's raw `syncStatus` with `status`; raw transport details remain internal to the store.
|
|
10
|
+
- Replace `useSync()` with `useAblo()`. The client is available during startup; await `client.ready()` before operations that require initialization.
|
|
11
|
+
- Replace `usePeers()` with `useAblo(ablo => ablo.presence.others)`. Replace scoped peer reads with `useAblo(ablo => ablo.presence.forModel(model, id))`; this returns sessions, including the current session when it has activity on that record. Filter by session ID when you specifically need other sessions.
|
|
12
|
+
- Replace `useMutationFailureListener(listener)` with `useEffect(() => ablo?.onMutationFailure(listener), [ablo, listener])`; the payload type is `Ablo.MutationFailure`.
|
|
13
|
+
- Handle provider startup errors with `<AbloProvider onError={...}>` instead of `useErrorListener()`.
|
|
14
|
+
- Read application identity from your authentication client or application context instead of `useCurrentUserId()`. The informational `AbloProvider.userId` prop is removed; authenticated Ablo authority still comes from the client's session.
|
|
15
|
+
- `GroupScope` leaves the React entry point with `usePeers`; presence filtering uses model and record arguments.
|
|
16
|
+
- `useSyncStore`, `SyncStoreContract`, and `useReactive` are no longer React entry-point exports. Use the core client; framework integration internals remain under the humans runtime boundary.
|
|
17
|
+
- `ClientSideSuspense` and `DefaultFallback` are no longer public components. Supply your own UI through the provider's `fallback` prop or render from `ablo.status`.
|
|
18
|
+
|
|
19
|
+
The React entry point explicitly exports its supported bindings instead of forwarding all implementation exports through a wildcard. Existing model operations, claims, and reading-activity lifetimes remain on their core owners.
|
|
20
|
+
|
|
21
|
+
React type annotations now follow their owners. The entry point has eight runtime exports (`Ablo`, `humans`, `AbloProvider`, `createAbloReact`, `useAblo`, `usePresence`, `useMutators`, `useUndoScope`) and no standalone type exports:
|
|
22
|
+
|
|
23
|
+
| Removed React type export | Replacement |
|
|
24
|
+
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `AbloOptions` | `Ablo.Options` |
|
|
26
|
+
| `AbloReads` | `Ablo.Reads` |
|
|
27
|
+
| `HumansSurface` | Infer the plugin result; low-level plugin authors can still import the type from `@abloatai/humans` |
|
|
28
|
+
| `AbloProviderProps` | `AbloProvider.Props` |
|
|
29
|
+
| `AbloReactBinding` | Infer the binding, or `ReturnType<typeof createAbloReact<Models>>` |
|
|
30
|
+
| `PresenceModelSelector` | Infer an inline selector against the bound schema |
|
|
31
|
+
| `UseAbloModelOptions` | `useAblo.Options<Row>` |
|
|
32
|
+
| `UseAbloModelResult` | `useAblo.Result<Row>` |
|
|
33
|
+
| `UseAbloHydratedModelResult` | `useAblo.Result<Row>`; handle optional data after local removal, even with `initial` |
|
|
34
|
+
| `InvokerFor` | `typeof mutations.model.operation` |
|
|
35
|
+
| `MutatorInvokers` | `useMutators.Result<typeof definitions>` |
|
|
36
|
+
| `UseMutatorsOptions` | `useMutators.Options<typeof schema>` |
|
|
37
|
+
| `UseUndoScopeResult` | `useUndoScope.Result<typeof schema>` |
|
|
38
|
+
| `ResolveSchema` | Existing core `Ablo.ResolveSchema`, when using module registration |
|
|
39
|
+
| `ResolveModelKey` | `keyof typeof schema.models` |
|
|
40
|
+
| `DefaultSyncShape`, `ResolveUserMeta`, `ResolveClaimMeta` | Internal registration details; use schema inference and the core presence/claim types |
|
|
41
|
+
| `MutateActions` | `Ablo.Mutator.Transaction<typeof schema>['mutate'][ModelKey]` |
|
|
42
|
+
| `ReaderActions` | `Ablo.Mutator.Transaction<typeof schema>['read'][ModelKey]` |
|
|
43
|
+
| `ReaderFindOptions` | Infer read options; advanced callers can import the existing type from `@abloatai/ablo/client` |
|
|
44
|
+
|
|
45
|
+
`ModelScope` is no longer re-exported by React; its existing advanced owner is `@abloatai/ablo/client`. Ordinary model reads use their `state` option.
|
|
46
|
+
|
|
47
|
+
`createAbloReact(schema)` now specializes only types and returns the existing module-level provider and hooks. It creates no React context or component identity. Hooks always read the nearest AbloProvider, including when bindings for different schemas are nested; do not call a binding's hooks below a provider for another schema. Recreating a binding no longer remounts its children. Keep the application binding at module scope for clear ownership.
|
|
48
|
+
|
|
49
|
+
Selectors now preserve equal snapshot identity, detach nested row data, and subscribe to claim events. Subscriptions survive suspended renders and catch changes between render and subscription. Initial rows match server rendering during hydration, then yield to local data; a later removal no longer restores the seed. Local cache absence remains distinct from server absence.
|
|
50
|
+
|
|
3
51
|
## 0.63.1
|
|
4
52
|
|
|
5
53
|
React workspaces can render immediately while collaboration connects.
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
/** Core client and the explicitly supported React bindings. */
|
|
1
2
|
export { Ablo, humans } from '@abloatai/humans';
|
|
2
|
-
export
|
|
3
|
-
export * from '@abloatai/humans/react';
|
|
3
|
+
export { AbloProvider, createAbloReact, useAblo, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
|
|
4
4
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChD,YAAY,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,YAAY,EACZ,eAAe,EACf,OAAO,EACP,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,wBAAwB,CAAC"}
|
package/dist/react.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Core client and the explicitly supported React bindings. */
|
|
1
2
|
export { Ablo, humans } from '@abloatai/humans';
|
|
2
|
-
export
|
|
3
|
+
export { AbloProvider, createAbloReact, useAblo, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
|
|
3
4
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,YAAY,EACZ,eAAe,EACf,OAAO,EACP,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,wBAAwB,CAAC"}
|
|
@@ -118,8 +118,8 @@ The example claims both `executionOwner` and `executionState`, writes with the
|
|
|
118
118
|
claim, simulates generating, then stays idle while still holding ownership.
|
|
119
119
|
The `executionOwner` row value is historical metadata after release; the UI uses
|
|
120
120
|
`useAblo(client => client.conversations, id).claimed` for reactive ownership.
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
Both the row form and `useAblo(client => client.conversations.claim.state({ id }))`
|
|
122
|
+
subscribe to claim events. Applications decide how to recover
|
|
123
123
|
an interrupted execution-state field; a lease is not proof of ongoing generation.
|
|
124
124
|
|
|
125
125
|
Text buffering, tool execution and queued-message scheduling belong to the
|
package/docs/groups.md
CHANGED
|
@@ -46,7 +46,7 @@ question you have.
|
|
|
46
46
|
ablo.records.onChange((docs) => render(docs));
|
|
47
47
|
|
|
48
48
|
// In React: who else is visible on this client's scoped groups?
|
|
49
|
-
const peers =
|
|
49
|
+
const peers = useAblo(ablo => ablo.presence.forModel('records', documentId)) ?? [];
|
|
50
50
|
|
|
51
51
|
// Stop this write if the thing I read moved while I composed it.
|
|
52
52
|
const record = await ablo.records.read({ id: 's-1' });
|
|
@@ -57,10 +57,10 @@ await ablo.blocks.update({ id, data, reads: [record] });
|
|
|
57
57
|
| Question | Channel | Arrives |
|
|
58
58
|
| --- | --- | --- |
|
|
59
59
|
| What do the rows say right now? | `onChange` | As deltas land, on the socket |
|
|
60
|
-
| Who else is working here? | `
|
|
60
|
+
| Who else is working here? | `useAblo(ablo => ablo.presence.others)` over the session/client groups | As participants connect, disconnect, or change activity |
|
|
61
61
|
| Did the premise for **this** write move? | `reads` on the write | On that write's receipt, before it applies |
|
|
62
62
|
|
|
63
|
-
`onChange` and `
|
|
63
|
+
`onChange` and `useAblo(ablo => ablo.presence.others)` use the reactive client's socket. `reads` rides the
|
|
64
64
|
commit, so it reaches a socketless actor over HTTP too. The row returned by
|
|
65
65
|
`read` privately carries its model, id, and
|
|
66
66
|
watermark; passing that row in `reads` is enough to protect a later write. Ablo
|
|
@@ -183,6 +183,6 @@ them coarse everywhere else.
|
|
|
183
183
|
[`concurrency-convention.md`](./concurrency-convention.md) (§4 and §5).
|
|
184
184
|
- **The mechanics**, the three coordination blocks underneath, are
|
|
185
185
|
[`coordination.md`](./coordination.md).
|
|
186
|
-
- **Presence** is read with `
|
|
186
|
+
- **Presence** is read with `useAblo(ablo => ablo.presence.others)`; active exclusions remain on the
|
|
187
187
|
`claim` namespace. See [`react.md`](./react.md) and
|
|
188
188
|
[`coordination.md`](./coordination.md).
|
package/docs/identity.md
CHANGED
|
@@ -585,7 +585,7 @@ an agent pointed at the entities it's working on. You **never hand-write**
|
|
|
585
585
|
So an agent's reachable set **accretes** as it works — no extra subscribe call.
|
|
586
586
|
|
|
587
587
|
3. **Presence follows those same groups.** A reactive client announces one
|
|
588
|
-
participant on its connection; `
|
|
588
|
+
participant on its connection; `ablo.presence.forModel(model, id)` filters the roster already
|
|
589
589
|
visible through the session and connection groups. There is no second
|
|
590
590
|
membership lease or participant handle. Use `claim` separately when work needs
|
|
591
591
|
exclusion. See [React](./react.md#usepeers-read-only-presence).
|
package/docs/react.md
CHANGED
|
@@ -16,6 +16,54 @@ The React bindings ship with the main package — no extra install.
|
|
|
16
16
|
import { useAblo } from '@abloatai/ablo/react';
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
React uses the same objects and operations as core Ablo. Read `ablo.status`
|
|
20
|
+
with `useAblo(ablo => ablo.status)`, select `ablo.presence.others` for other
|
|
21
|
+
sessions, and call ordinary model methods from event handlers. React owns the
|
|
22
|
+
subscription or component lifetime; the client owns the behavior.
|
|
23
|
+
|
|
24
|
+
For mutation failures, subscribe to the core event in an effect:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
const ablo = useAblo();
|
|
28
|
+
useEffect(() => ablo?.onMutationFailure(reportFailure), [ablo, reportFailure]);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`reportFailure` receives `Ablo.MutationFailure`. Read application identity from
|
|
32
|
+
your auth provider; Ablo's authenticated authority comes from its session.
|
|
33
|
+
|
|
34
|
+
## Types follow their owners
|
|
35
|
+
|
|
36
|
+
Most calls infer their types from your schema. For wrapper components or explicit
|
|
37
|
+
annotations, use the name you already imported:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Ablo, AbloProvider, useAblo } from '@abloatai/ablo/react';
|
|
41
|
+
import { schema } from './schema';
|
|
42
|
+
|
|
43
|
+
type Models = (typeof schema)['models'];
|
|
44
|
+
type Chat = Ablo.Schema.InferRow<typeof schema, 'chats'>;
|
|
45
|
+
|
|
46
|
+
function Provider(props: AbloProvider.Props<Models>) {
|
|
47
|
+
return <AbloProvider {...props} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ChatView({ result }: { result: useAblo.Result<Chat> }) {
|
|
51
|
+
return <p>{result.data?.title}</p>;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The React annotation types are `AbloProvider.Props`, `useAblo.Options`,
|
|
56
|
+
`useAblo.Result`, `useMutators.Options`, `useMutators.Result` and
|
|
57
|
+
`useUndoScope.Result`. Core types remain under `Ablo`, including `Ablo.Options`,
|
|
58
|
+
`Ablo.Reads`, `Ablo.Status` and `Ablo.PresenceSession`. Import annotation owners
|
|
59
|
+
from the SDK entry point; functions destructured from an app binding infer their
|
|
60
|
+
call types but do not create new TypeScript namespaces in the app module.
|
|
61
|
+
|
|
62
|
+
`createAbloReact(schema)` specializes types and returns the existing provider and
|
|
63
|
+
hooks. Define your binding at module scope. It creates no client, React context
|
|
64
|
+
or component identity. Hooks always read the nearest provider: use the binding
|
|
65
|
+
that matches that provider's client schema.
|
|
66
|
+
|
|
19
67
|
## Building the client
|
|
20
68
|
|
|
21
69
|
You build the Ablo client once — that's where the schema, the session endpoint,
|
|
@@ -135,19 +183,19 @@ for the complete ownership boundary and runnable component.
|
|
|
135
183
|
|
|
136
184
|
## Render immediately with connection status
|
|
137
185
|
|
|
138
|
-
`
|
|
186
|
+
`useAblo(ablo => ablo.status)` works during provider startup, in passthrough children and in
|
|
139
187
|
custom fallbacks. It observes the client's status before row scope is available.
|
|
140
188
|
It still requires a provider. Data hooks that require authenticated scope must
|
|
141
189
|
wait for readiness.
|
|
142
190
|
|
|
143
191
|
```tsx
|
|
144
|
-
import {
|
|
192
|
+
import { useAblo } from '@abloatai/ablo/react';
|
|
145
193
|
|
|
146
194
|
function ConnectionIndicator() {
|
|
147
|
-
const status =
|
|
195
|
+
const status = useAblo(ablo => ablo.status);
|
|
148
196
|
return <span role="status">{
|
|
149
|
-
status
|
|
150
|
-
? 'Connecting…' : status
|
|
197
|
+
status?.name === 'initial' || status?.name === 'connecting'
|
|
198
|
+
? 'Connecting…' : status?.name
|
|
151
199
|
}</span>;
|
|
152
200
|
}
|
|
153
201
|
|
|
@@ -171,6 +219,7 @@ export function ReportView({ report: serverReport }: { report: { id: string; loc
|
|
|
171
219
|
{ initial: serverReport },
|
|
172
220
|
);
|
|
173
221
|
|
|
222
|
+
if (!report) return <p>This report is not in the local cache.</p>;
|
|
174
223
|
return <article>{report.location}{claimed && <span>Claimed</span>}</article>;
|
|
175
224
|
}
|
|
176
225
|
```
|
|
@@ -180,8 +229,24 @@ The row form subscribes to both data and claim events. It returns `data`,
|
|
|
180
229
|
|
|
181
230
|
For data-only reads, selectors such as
|
|
182
231
|
`useAblo(ablo => ablo.weatherReports.local.get(id))` track model fields.
|
|
183
|
-
|
|
184
|
-
|
|
232
|
+
Claim selectors subscribe too: `useAblo(ablo => ablo.weatherReports.claim.state({ id }))`
|
|
233
|
+
updates when ownership changes. Use the row form when you want data and claims together.
|
|
234
|
+
|
|
235
|
+
Selectors must be pure synchronous reads. Ablo copies selected rows, arrays and plain
|
|
236
|
+
records into frozen snapshots, including nested data and computed fields. Unchanged
|
|
237
|
+
data retains its snapshot identity across renders. Treat copied dates as read-only;
|
|
238
|
+
model API handles and other class instances retain their original identity. Read
|
|
239
|
+
data inside the selector to subscribe to it. Selector errors reach React error boundaries.
|
|
240
|
+
|
|
241
|
+
`initial` supplies the server HTML and first hydration render; React then reads the
|
|
242
|
+
current local row. Once a local row has appeared, removing it returns `undefined`
|
|
243
|
+
instead of restoring the seed, so `data` remains optional even with `initial`.
|
|
244
|
+
|
|
245
|
+
A local cache miss (`undefined` or an empty list) does not prove that the server has
|
|
246
|
+
no matching data. Local reads do not fetch rows, and connection status does not
|
|
247
|
+
indicate query completeness. Use your route loader and an awaited server read, such
|
|
248
|
+
as `await ablo.weatherReports.get({ id })`, to establish server results and handle
|
|
249
|
+
request errors before passing an initial row to React.
|
|
185
250
|
|
|
186
251
|
Use the zero-argument form only when you need the full client for callbacks,
|
|
187
252
|
effects, or writes:
|
|
@@ -339,30 +404,28 @@ durable model fields when state must survive reconnects. The sending connection
|
|
|
339
404
|
does not receive its own event. Coalesce or throttle pointer movement in the
|
|
340
405
|
application; model events do not currently declare a per-event `maxHz`.
|
|
341
406
|
|
|
342
|
-
##
|
|
407
|
+
## Presence: the same core read
|
|
343
408
|
|
|
344
|
-
`
|
|
345
|
-
|
|
346
|
-
|
|
409
|
+
Select `ablo.presence.others` for the other sessions visible to this client, or
|
|
410
|
+
filter by a model and record. These reads subscribe to changes without starting
|
|
411
|
+
an activity or claiming ownership.
|
|
347
412
|
|
|
348
413
|
```tsx
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
import { usePeers } from '@abloatai/ablo/react';
|
|
414
|
+
import { useAblo } from '@abloatai/ablo/react';
|
|
352
415
|
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
416
|
+
function Readers({ conversationId }: { conversationId: string }) {
|
|
417
|
+
const sessions = useAblo(ablo => ablo.presence.forModel('conversations', conversationId)) ?? [];
|
|
418
|
+
const people = new Set(sessions.filter(session => session.participant.kind === 'user')
|
|
419
|
+
.map(session => session.participant.id));
|
|
420
|
+
return <span>{people.size} people, {sessions.length} sessions</span>;
|
|
357
421
|
}
|
|
358
422
|
```
|
|
359
423
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
not claim anything.
|
|
424
|
+
`usePresence(ablo => ablo.conversations, conversationId)` additionally owns a
|
|
425
|
+
reading activity for the component's lifetime. Use it when mounting the component
|
|
426
|
+
should announce that this session is reading the record. Core code owns the same
|
|
427
|
+
lifecycle explicitly with `ablo.conversations.presence.read(conversationId)` and
|
|
428
|
+
its returned cleanup function.
|
|
366
429
|
|
|
367
430
|
## Next.js
|
|
368
431
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/ablo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"description": "The public Ablo SDK for coordinated reads, commits, claims, observation, and reactive applications.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
"directory": "packages/ablo"
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"@abloatai/humans": "^0.
|
|
149
|
-
"@abloatai/transaction": "^0.
|
|
148
|
+
"@abloatai/humans": "^0.64.0",
|
|
149
|
+
"@abloatai/transaction": "^0.64.0",
|
|
150
150
|
"zod": "^4.4.3"
|
|
151
151
|
},
|
|
152
152
|
"peerDependencies": {
|