@abloatai/ablo 0.64.0 → 0.64.1

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.64.1
4
+
5
+ The React read and write boundaries are now explicit: `useAbloClient()` returns the writable client and `useAblo(selector)` returns render snapshots. The zero-argument `useAblo()` overload is removed. `ablo upgrade` reports migration hints; see [Upgrade Guide](./docs/migration.md).
6
+
7
+ Public schema registration reaches Transaction and Humans across emitted package declarations. Ambient mutators without a registered schema now report a schema diagnostic; prefer explicit-schema overloads. Internal package dependencies are exact, and CLI/SDK compatibility is checked as one release family.
8
+
9
+ `usePresence(..., { excludeSelf: true })` and `presence.forModel(model, id, { excludeSelf: true })` omit the current session. `useMutationFailure` owns React subscription cleanup. Custom framework adapters use `getAbloStore(client)` from `/client`. Generic collaboration subscriptions preserve their declared event tuples; attributed handlers use `collaboration.subscribe` (or transport `subscribeCollaboration`).
10
+
11
+ `AbloProvider` is now the sole provider vocabulary. Advanced framework adapters use `useAbloStoreContext` and `AbloStoreContextValue`; the missing-provider error code is `ablo_context_missing_provider`.
12
+
3
13
  ## 0.64.0
4
14
 
5
15
  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.
package/dist/index.d.ts CHANGED
@@ -8,4 +8,12 @@ export { Ablo as default } from '@abloatai/transaction';
8
8
  */
9
9
  export { noopLogger } from '@abloatai/transaction/logger';
10
10
  export type { Logger } from '@abloatai/transaction/logger';
11
+ /** Application registration, owned here so augmentation survives re-exports. */
12
+ export interface Register {
13
+ }
14
+ type PublicRegister = Register;
15
+ declare module '@abloatai/transaction/types/global' {
16
+ interface Register extends PublicRegister {
17
+ }
18
+ }
11
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,YAAY,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,YAAY,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAE3D,gFAAgF;AAEhF,MAAM,WAAW,QAAQ;CAAG;AAC5B,KAAK,cAAc,GAAG,QAAQ,CAAC;AAI/B,OAAO,QAAQ,oCAAoC,CAAC;IAClD,UAAU,QAAS,SAAQ,cAAc;KAAG;CAC7C"}
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** Core client and the explicitly supported React bindings. */
2
2
  export { Ablo, humans } from '@abloatai/humans';
3
- export { AbloProvider, createAbloReact, useAblo, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
3
+ export { AbloProvider, createAbloReact, useAblo, useAbloClient, useMutationFailure, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
4
4
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
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"}
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,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,wBAAwB,CAAC"}
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /** Core client and the explicitly supported React bindings. */
2
2
  export { Ablo, humans } from '@abloatai/humans';
3
- export { AbloProvider, createAbloReact, useAblo, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
3
+ export { AbloProvider, createAbloReact, useAblo, useAbloClient, useMutationFailure, usePresence, useMutators, useUndoScope, } from '@abloatai/humans/react';
4
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,+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"}
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,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,YAAY,GACb,MAAM,wBAAwB,CAAC"}
@@ -115,11 +115,11 @@ the agent can never reach a workspace its triggering user couldn't.
115
115
 
116
116
  ## 3. Write: it fans out to everyone on that workspace
117
117
 
118
- Inside any component under the provider, grab the scoped client with `useAblo()`
118
+ Inside any component under the provider, grab the scoped client with `useAbloClient()`
119
119
  and write. The connection is already narrowed to `workspace:<workspaceId>` from Step 2.
120
120
 
121
121
  ```ts
122
- const ablo = useAblo();
122
+ const ablo = useAbloClient();
123
123
 
124
124
  // Other participants subscribed to workspace:<workspaceId> — a reviewer agent, a
125
125
  // person watching in the UI — receive this delta in realtime. Participants on
@@ -287,7 +287,7 @@ In React, selector `useAblo` is the public read API:
287
287
  ```tsx
288
288
  'use client';
289
289
 
290
- import { useAblo } from '@abloatai/ablo/react';
290
+ import { useAblo, useAbloClient } from '@abloatai/ablo/react';
291
291
 
292
292
  export function ReportRow({
293
293
  report: serverReport,
@@ -301,10 +301,10 @@ export function ReportRow({
301
301
  }
302
302
  ```
303
303
 
304
- Use zero-argument `useAblo()` only in callbacks and effects:
304
+ Call `useAbloClient()` during render, then use its client in callbacks and effects:
305
305
 
306
306
  ```tsx
307
- const ablo = useAblo();
307
+ const ablo = useAbloClient();
308
308
  ```
309
309
 
310
310
  ## 4. Write State
package/docs/migration.md CHANGED
@@ -3,7 +3,100 @@
3
3
  > A safe workflow for upgrading the pre-1.0 SDK.
4
4
 
5
5
  Ablo is pre-1.0, so a minor release may contain a breaking API change. Patch
6
- releases remain compatible within the same minor line.
6
+ releases normally remain compatible within the same minor line. The 0.64.1
7
+ release includes the explicit React and collaboration migrations below.
8
+
9
+ ## React and package boundaries after 0.64
10
+
11
+ Run `npx ablo upgrade` for migration hints before editing. It previews changes;
12
+ `--write` applies only mechanical rewrites. React changes requiring application
13
+ judgment are reported with their replacements.
14
+
15
+ | Previous API | Current API |
16
+ | --- | --- |
17
+ | `useSync()` or zero-argument `useAblo()` | `useAbloClient()` for actions; await `client.ready()` when initialization is required |
18
+ | `useSyncStatus()` | `useAblo(client => client.status)` |
19
+ | `usePeers()` | `useAblo(client => client.presence.others)` |
20
+ | Scoped peers | `usePresence(client => client.records, id, { excludeSelf: true })` |
21
+ | `useMutationFailureListener(listener)` | `useMutationFailure(listener)` |
22
+ | `useErrorListener()` | `AbloProvider`'s `onError` prop for startup errors |
23
+ | `useCurrentUserId()` | Your application's authentication context |
24
+ | `useSDKSyncStore()` / `useSyncStore()` | Public client operations; `getAbloStore(client)` from `/client` for custom local-store adapters |
25
+ | `ClientSideSuspense` / `DefaultFallback` | Application UI through the provider's `fallback` prop |
26
+ | `GroupScope` for presence | Model and record arguments |
27
+
28
+ `useAblo(selector)` returns detached snapshots for rendering. They have no model
29
+ methods or relation accessors. `useAbloClient()` returns the writable client and
30
+ does not subscribe to row changes. Do not perform writes inside a selector.
31
+
32
+ ### Presence, activities and claims
33
+
34
+ `Activity` is replaced by `Ablo.PresenceActivity`, including for agents. A session
35
+ is `Ablo.PresenceSession`: identify the execution with `presenceSessionId` and its
36
+ actor with `participant.id` and `participant.kind`. A participant can have several
37
+ sessions, so exclude self by session rather than user ID.
38
+
39
+ `presence.active` contains this session's activities; `presence.others` contains
40
+ other sessions, each with an `activities` array. `forModel(model, id)` includes
41
+ self by default; pass `{ excludeSelf: true }` as its third argument to omit self.
42
+ The same option on `usePresence` still announces the component's reading activity
43
+ and cleans it up on unmount.
44
+
45
+ An activity has `operation`, `target`, `source`, and ISO timestamp fields.
46
+ Read the model and row from `activity.target.model` and `.id`; field-specific
47
+ activity uses `.field` or `.fields` (never both, and always with a row ID).
48
+ Activities describe visible work; they do not grant write authority.
49
+
50
+ Claims are acquired through `await using claim = await client.records.claim({ id })`.
51
+ The protected row is `claim.data`; pass `claim` to the write. Claim lookup uses
52
+ `client.records.claim.state({ id })` and `.queue({ id })`. Claim metadata is on
53
+ `claim.target.meta`, and a multi-field claim uses `fields` in the claim target.
54
+ Do not reconstruct a claim handle from a presence activity. See [Claims](./claims.md).
55
+
56
+ ### Imports and schema ownership
57
+
58
+ The root factory creates a headless client; `/client` creates the reactive client.
59
+ Import the `Ablo` type namespace beside the factory you use. Both expose the
60
+ shared schema, presence-activity and claim types. Reactive-only types such as
61
+ `Ablo.Status`, `Ablo.Reads`, `Ablo.Store`, and `Ablo.Mutator` belong to `/client`
62
+ (or `/react` for its re-exported reactive factory).
63
+
64
+ Prefer explicit schema inference:
65
+
66
+ ```ts
67
+ import { defineMutators } from '@abloatai/ablo/client';
68
+ import { useMutators, useUndoScope } from '@abloatai/ablo/react';
69
+ import { schema } from './schema';
70
+
71
+ const definitions = defineMutators(schema, {
72
+ records: {
73
+ rename: async ({ tx, args }: {
74
+ tx: import('@abloatai/ablo/client').Ablo.Mutator.Transaction<typeof schema>;
75
+ args: { id: string; title: string };
76
+ }) => tx.mutations.records.update({ id: args.id, title: args.title }),
77
+ },
78
+ });
79
+
80
+ function useRecordActions() {
81
+ const { scope } = useUndoScope(schema, 'record-editor');
82
+ return useMutators(schema, definitions, { undoScope: scope });
83
+ }
84
+ ```
85
+
86
+ Ambient registration remains available for a single application. Its module must
87
+ be included in that TypeScript program and import `@abloatai/ablo` before
88
+ augmenting `Register`. The public registration now reaches the downstream
89
+ Transaction and Humans resolvers through the emitted declarations. A package
90
+ compiled independently cannot inherit a consuming app's ambient declaration.
91
+
92
+ ### Structural collaboration adapters
93
+
94
+ Generic `subscribe(event, handler)` preserves exactly the argument tuples in your
95
+ event map. Receive optional server attribution through the session's
96
+ `collaboration.subscribe(event, (payload, context) => ...)`; low-level transport
97
+ adapters use `subscribeCollaboration`. Model-scoped `client.records.events.on`
98
+ continues to supply authenticated context as its second callback argument.
99
+ Legacy servers may omit context on application events; handle `undefined` there.
7
100
 
8
101
  ## Upgrade safely
9
102
 
@@ -14,7 +107,7 @@ releases remain compatible within the same minor line.
14
107
  5. Run the three-state deployment plan, then push the exact reviewed plan.
15
108
 
16
109
  ```bash
17
- npm install @abloatai/ablo@0.48
110
+ npm install --save-exact @abloatai/ablo@<version> @abloatai/cli@<version>
18
111
  npx ablo docs
19
112
  npx ablo docs api
20
113
  npx ablo plan
package/docs/react.md CHANGED
@@ -13,7 +13,7 @@ agents, start with [Integration Guide](/docs/integration-guide).
13
13
  The React bindings ship with the main package — no extra install.
14
14
 
15
15
  ```ts
16
- import { useAblo } from '@abloatai/ablo/react';
16
+ import { useAblo, useAbloClient } from '@abloatai/ablo/react';
17
17
  ```
18
18
 
19
19
  React uses the same objects and operations as core Ablo. Read `ablo.status`
@@ -21,11 +21,12 @@ with `useAblo(ablo => ablo.status)`, select `ablo.presence.others` for other
21
21
  sessions, and call ordinary model methods from event handlers. React owns the
22
22
  subscription or component lifetime; the client owns the behavior.
23
23
 
24
- For mutation failures, subscribe to the core event in an effect:
24
+ For mutation failures, let the hook own the subscription:
25
25
 
26
26
  ```tsx
27
- const ablo = useAblo();
28
- useEffect(() => ablo?.onMutationFailure(reportFailure), [ablo, reportFailure]);
27
+ import { useMutationFailure } from '@abloatai/ablo/react';
28
+
29
+ useMutationFailure(reportFailure);
29
30
  ```
30
31
 
31
32
  `reportFailure` receives `Ablo.MutationFailure`. Read application identity from
@@ -85,12 +86,12 @@ export const ablo = Ablo({
85
86
  });
86
87
 
87
88
  // The typed binding: capture the schema once, and every component imports
88
- // born-typed hooks from this file — `useAblo()` takes no type arguments,
89
+ // born-typed hooks from this file — `useAbloClient()` takes no type arguments,
89
90
  // and a selector's `ablo` parameter knows your models.
90
- export const { AbloProvider, useAblo, usePresence } = createAbloReact(schema);
91
+ export const { AbloProvider, useAblo, useAbloClient, usePresence, useMutationFailure } = createAbloReact(schema);
91
92
  ```
92
93
 
93
- Import `AbloProvider`, `useAblo`, and `usePresence` from `lib/ablo` rather than from the
94
+ Import `AbloProvider`, `useAbloClient`, `useAblo`, and `usePresence` from `lib/ablo` rather than from the
94
95
  package, and the schema generic never appears at a call site again — the
95
96
  same one-binding-file convention as tRPC's `createTRPCReact` or
96
97
  react-redux's typed hooks.
@@ -252,7 +253,7 @@ Use the zero-argument form only when you need the full client for callbacks,
252
253
  effects, or writes:
253
254
 
254
255
  ```tsx
255
- const abloClient = useAblo();
256
+ const abloClient = useAbloClient();
256
257
  ```
257
258
 
258
259
  Prefer selector reads like `useAblo((ablo) => ablo.<model>.local.get(id))`. Older hooks
@@ -302,7 +303,7 @@ For client event handlers, get the application-owned client and call the same
302
303
  model client:
303
304
 
304
305
  ```tsx
305
- const ablo = useAblo();
306
+ const ablo = useAbloClient();
306
307
 
307
308
  async function markReady() {
308
309
  if (!ablo) return;
@@ -376,7 +377,7 @@ signals. The model and record choose the authorized sync group; the payload
376
377
  does not need routing fields or caller-authored identity.
377
378
 
378
379
  ```tsx
379
- const ablo = useAblo();
380
+ const ablo = useAbloClient();
380
381
 
381
382
  useEffect(() => {
382
383
  if (!ablo) return;
@@ -431,3 +432,46 @@ its returned cleanup function.
431
432
 
432
433
  The Next.js [App Router landing](./examples/nextjs.md) walks through Server Components
433
434
  + Server Actions + `useAblo` together.
435
+
436
+ ## Separate packages and monorepos
437
+
438
+ Put the schema in an application-owned package, with a public export for its
439
+ value and type. Put the React binding in a client module which imports that
440
+ schema, and export its inferred declarations. Components import their hooks from
441
+ that binding package. Build the schema package before its dependents and test the
442
+ emitted declarations without workspace source aliases.
443
+
444
+ ```ts
445
+ 'use client';
446
+ import { createAbloReact } from '@abloatai/ablo/react';
447
+ import { schema } from '@app/schema';
448
+
449
+ export const { AbloProvider, useAbloClient, useAblo, usePresence, useMutationFailure } =
450
+ createAbloReact(schema);
451
+ ```
452
+
453
+ Use one binding per schema, and mount its hooks under a provider for that schema.
454
+ The binding specializes types; it does not create an isolated runtime context or
455
+ validate that a different binding's provider has the same schema. Keep React and
456
+ the Ablo package family deduplicated across packages. Reusable libraries should
457
+ accept a schema or typed binding rather than declare an application-global schema.
458
+ An app's ambient registration cannot retroactively change a separately compiled
459
+ library. Use `defineMutators(schema, definitions)`, `useMutators(schema, definitions)`
460
+ and `useUndoScope(schema, name)` across those boundaries.
461
+
462
+ ## Mutation failures and scoped collaborators
463
+
464
+ ```tsx
465
+ const peers = usePresence(client => client.records, id, { excludeSelf: true });
466
+ useMutationFailure(({ error }) => showToast(error.message));
467
+ ```
468
+
469
+ `useMutationFailure` uses the latest committed callback, moves its subscription
470
+ when the provider client changes, and unsubscribes on unmount. For non-React
471
+ callers, `client.onMutationFailure` returns the cleanup function.
472
+
473
+ For a custom framework adapter, `getAbloStore(client)` from
474
+ `@abloatai/ablo/client` returns the supported `Ablo.Store` contract: local pool
475
+ access, mutation observation, and scope management. Ordinary writes, confirmation,
476
+ reconnection and model events already have public client methods; they do not
477
+ require a store adapter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.64.0",
3
+ "version": "0.64.1",
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",
@@ -132,7 +132,8 @@
132
132
  "build:docs": "node scripts/build-blume-docs.mjs",
133
133
  "lint:docs-site": "node scripts/build-blume-docs.mjs --check",
134
134
  "lint:docs": "node scripts/check-doc-drift.mjs",
135
- "lint:pkg": "publint"
135
+ "lint:pkg": "publint",
136
+ "verify:package-contract": "node scripts/package-contract/index.mjs"
136
137
  },
137
138
  "publishConfig": {
138
139
  "access": "public",
@@ -145,8 +146,8 @@
145
146
  "directory": "packages/ablo"
146
147
  },
147
148
  "dependencies": {
148
- "@abloatai/humans": "^0.64.0",
149
- "@abloatai/transaction": "^0.64.0",
149
+ "@abloatai/humans": "0.64.1",
150
+ "@abloatai/transaction": "0.64.1",
150
151
  "zod": "^4.4.3"
151
152
  },
152
153
  "peerDependencies": {