@abloatai/ablo 0.11.2 → 0.12.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Canonicalize the claim API to one vocabulary, plus DX fixes (breaking).
8
+ - BREAKING: claim phase field `action` → `reason` on every claim surface
9
+ (`Claim`, `ClaimHandle`, `ClaimCreateOptions`, `ModelClaim`, ...). The wire
10
+ is unchanged (still `action`, healed on read) — no server redeploy needed.
11
+ - BREAKING: claim contention flag `wait` → `queue` (one word everywhere).
12
+ - BREAKING: React hook `useParticipant` → `useWatch` (aligns with `ablo.<model>.watch`).
13
+ - `ClaimDeclaration.ttlSeconds` is now `number` (was a `Duration`).
14
+ - Docs: `retrieve` HTTP envelope (`.data`/`.stamp`) called out; `syncGroups`
15
+ reworded (provisional, not deprecated); `orgScoped` cross-tenant security
16
+ warning; React error strings point at `<AbloProvider>`.
17
+
3
18
  ## 0.11.2
4
19
 
5
20
  ### Patch Changes
@@ -58,10 +58,11 @@ export interface ClaimBroadcastMiddlewareOptions<R extends SchemaRecord = Schema
58
58
  /** Target entity. Null skips the broadcast (purely conversational). */
59
59
  readonly target: ClaimTarget | null;
60
60
  /**
61
- * Action verb describing what the agent is doing. Convention:
62
- * `'edit'`, `'read'`, `'review'`, `'generate'`. Default `'edit'`.
61
+ * Human-readable phase describing what the agent is doing. Convention:
62
+ * `'edit'`, `'read'`, `'review'`, `'generate'`. Default `'edit'`. The same
63
+ * `reason` field used on every claim surface.
63
64
  */
64
- readonly action?: string;
65
+ readonly reason?: string;
65
66
  /**
66
67
  * Peer-visible explanation of the specific work this model call is about to
67
68
  * perform. Surfaces to other agents through `ActiveClaim.description`.
@@ -29,7 +29,7 @@
29
29
  */
30
30
  export function claimBroadcastMiddleware(options) {
31
31
  const { agent, target } = options;
32
- const action = options.action ?? 'edit';
32
+ const reason = options.reason ?? 'edit';
33
33
  const description = options.description;
34
34
  const openClaim = () => {
35
35
  if (!agent || !target)
@@ -42,7 +42,7 @@ export function claimBroadcastMiddleware(options) {
42
42
  field: target.field,
43
43
  meta: target.meta,
44
44
  }, {
45
- reason: action,
45
+ reason,
46
46
  description,
47
47
  ttl: target.estimatedMs ?? 60_000,
48
48
  });
@@ -14,7 +14,7 @@
14
14
  * model: anthropic('claude-opus-4-7'),
15
15
  * agent,
16
16
  * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
17
- * action: 'renaming',
17
+ * reason: 'renaming',
18
18
  * description: 'Renaming the deck title to match the project brief.',
19
19
  * });
20
20
  *
@@ -40,10 +40,11 @@ export interface WrapWithMultiplayerOptions {
40
40
  /** Target entity. Null = pass-through wrap. */
41
41
  readonly target: ClaimTarget | null;
42
42
  /**
43
- * Optional action verb for the broadcast. Default `'edit'`.
44
- * Convention: `'edit'`, `'read'`, `'review'`, `'generate'`.
43
+ * Optional human-readable phase for the broadcast. Default `'edit'`.
44
+ * Convention: `'edit'`, `'read'`, `'review'`, `'generate'`. The same
45
+ * `reason` field used on every claim surface.
45
46
  */
46
- readonly action?: string;
47
+ readonly reason?: string;
47
48
  /**
48
49
  * Peer-visible explanation of the specific work this model call is about to
49
50
  * perform. Other agents receive it in their coordination context.
@@ -14,7 +14,7 @@
14
14
  * model: anthropic('claude-opus-4-7'),
15
15
  * agent,
16
16
  * target: { entityType: 'SlideDeck', entityId: 'deck-abc' },
17
- * action: 'renaming',
17
+ * reason: 'renaming',
18
18
  * description: 'Renaming the deck title to match the project brief.',
19
19
  * });
20
20
  *
@@ -31,12 +31,12 @@ import { wrapLanguageModel } from 'ai';
31
31
  import { claimBroadcastMiddleware, } from './claim-broadcast.js';
32
32
  import { coordinationContextMiddleware } from './coordination-context.js';
33
33
  export function wrapWithMultiplayer(options) {
34
- const { model, agent, target, action, description, excludeClaimIds, extraMiddleware } = options;
34
+ const { model, agent, target, reason, description, excludeClaimIds, extraMiddleware } = options;
35
35
  return wrapLanguageModel({
36
36
  model,
37
37
  middleware: [
38
38
  coordinationContextMiddleware({ agent, target, excludeClaimIds }),
39
- claimBroadcastMiddleware({ agent, target, action, description }),
39
+ claimBroadcastMiddleware({ agent, target, reason, description }),
40
40
  ...(extraMiddleware ?? []),
41
41
  ],
42
42
  });
package/dist/cli.cjs CHANGED
@@ -277121,7 +277121,9 @@ var modelClaimSchema = import_zod3.z.object({
277121
277121
  id: import_zod3.z.string(),
277122
277122
  actor: import_zod3.z.string(),
277123
277123
  participantKind: wireParticipantKindSchema,
277124
- action: import_zod3.z.string(),
277124
+ /** Human-readable phase (`'editing'`). The public SDK field; the WS/HTTP
277125
+ * wire carries the same value as `action` (healed on read). */
277126
+ reason: import_zod3.z.string(),
277125
277127
  description: import_zod3.z.string().optional(),
277126
277128
  field: import_zod3.z.string().optional(),
277127
277129
  status: import_zod3.z.enum(["active", "queued"]).optional(),
@@ -332,8 +332,14 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
332
332
  */
333
333
  configOverrides?: Partial<SyncEngineConfig>;
334
334
  /**
335
- * @deprecated Server derives sync groups from the apiKey's scope.
336
- * Required today as a runtime holdover; removed once Phase 3 ships.
335
+ * Sync groups (entity scopes) this client subscribes to. **Provisional, not
336
+ * deprecated** pick the right lane: normally the server derives these from
337
+ * the apiKey's scope, but passing them is still REQUIRED today in any config
338
+ * where the key doesn't resolve them (omitting yields a `degenerate
339
+ * syncGroups` warning and a zero-fan-out client). Keep passing it explicitly
340
+ * until the server-derived path ships in Phase 3, at which point it becomes a
341
+ * true no-op and is removed. Build values with `syncGroup(kind, id)` from
342
+ * `@abloatai/ablo/schema`.
337
343
  */
338
344
  syncGroups?: string[];
339
345
  /**
@@ -388,7 +394,9 @@ export interface ModelReadOptions extends ClaimedOptions {
388
394
  }
389
395
  export interface ClaimCreateOptions {
390
396
  readonly target: ModelTarget;
391
- readonly action: string;
397
+ /** Human-readable phase shown to peers — `'editing'`, `'writing'`. The same
398
+ * word on every claim surface; serialized on the wire as `action`. */
399
+ readonly reason: string;
392
400
  readonly ttl?: Duration;
393
401
  /**
394
402
  * Join the server's fair FIFO queue when the target is already claimed,
@@ -490,6 +498,20 @@ export interface HttpClaimApi<T> {
490
498
  reorder(params: ClaimReorderParams<T>): Promise<void>;
491
499
  }
492
500
  export interface ModelClient<T = Record<string, unknown>> {
501
+ /**
502
+ * Single-row read over HTTP. **Returns an envelope, not the bare row** — the
503
+ * row is on `.data`, alongside the `.stamp` watermark (for stale-context
504
+ * guards on the following write) and any active `.claims`. A stateless HTTP
505
+ * client can't synthesize the watermark from a local snapshot, so the
506
+ * envelope is load-bearing here (the WebSocket client's `retrieve` returns
507
+ * `T | undefined` because it reads from the hydrated pool).
508
+ *
509
+ * ```ts
510
+ * const deal = await ablo.deals.retrieve({ id });
511
+ * deal.data?.recommendation; // ← the row is on .data
512
+ * deal.stamp; // watermark — pass to the next write's readAt
513
+ * ```
514
+ */
493
515
  retrieve(params: ModelReadOptions & {
494
516
  readonly id: string;
495
517
  }): Promise<ModelRead<T>>;
@@ -1124,7 +1124,7 @@ export function Ablo(options) {
1124
1124
  id: claim.id,
1125
1125
  actor: claim.heldBy,
1126
1126
  participantKind: claim.participantKind,
1127
- action: claim.reason,
1127
+ reason: claim.reason,
1128
1128
  ...(description ? { description } : {}),
1129
1129
  field: claim.target.field,
1130
1130
  status: 'active',
@@ -1144,7 +1144,7 @@ export function Ablo(options) {
1144
1144
  id: claim.id,
1145
1145
  actor: claim.heldBy,
1146
1146
  participantKind: claim.participantKind,
1147
- action: claim.action,
1147
+ reason: claim.reason,
1148
1148
  ...(claim.description ? { description: claim.description } : {}),
1149
1149
  field: claim.target.field,
1150
1150
  status: 'queued',
@@ -1246,7 +1246,7 @@ export function Ablo(options) {
1246
1246
  return {
1247
1247
  object: 'claim',
1248
1248
  claimId: claim.claimId,
1249
- action: claim.action,
1249
+ reason: claim.reason,
1250
1250
  target: claim.target,
1251
1251
  waited,
1252
1252
  release,
@@ -1265,7 +1265,7 @@ export function Ablo(options) {
1265
1265
  field: claimOptions.target.field,
1266
1266
  meta: claimOptions.target.meta,
1267
1267
  }, {
1268
- reason: claimOptions.action,
1268
+ reason: claimOptions.reason,
1269
1269
  ttl: claimOptions.ttl,
1270
1270
  queue: claimOptions.queue,
1271
1271
  });
@@ -1348,7 +1348,7 @@ export function Ablo(options) {
1348
1348
  ...(held.target.field ? { field: held.target.field } : {}),
1349
1349
  ...(held.target.meta ? { meta: held.target.meta } : {}),
1350
1350
  },
1351
- action: held.action,
1351
+ reason: held.reason,
1352
1352
  heldBy: held.actor,
1353
1353
  participantKind: held.participantKind,
1354
1354
  expiresAt: held.expiresAt,
@@ -11,6 +11,18 @@ import { registerDataSource } from './registerDataSource.js';
11
11
  import { toSeconds } from '../utils/duration.js';
12
12
  import { mintSession } from './sessionMint.js';
13
13
  import { assertWriteOptions } from './writeOptionsSchema.js';
14
+ /**
15
+ * The `/v1/claims` and model-query routes still emit the wire field `action`
16
+ * for the claim phase; the public `Claim` / `ModelClaim` expose it as `reason`.
17
+ * Heal on read so the SDK shape is consistent without a coordinated server
18
+ * deploy — `reason ?? action`. When the server adopts `reason`, this is a no-op.
19
+ */
20
+ function healClaimPhase(claim) {
21
+ const raw = claim;
22
+ if (raw.reason !== undefined)
23
+ return claim;
24
+ return { ...claim, reason: raw.action ?? 'editing' };
25
+ }
14
26
  const DEFAULT_AGENT_LEASE = '10m';
15
27
  export function createProtocolClient(options) {
16
28
  const env = readProcessEnv();
@@ -173,8 +185,8 @@ export function createProtocolClient(options) {
173
185
  const suffix = params.toString();
174
186
  const body = await requestJson(`/v1/claims${suffix ? `?${suffix}` : ''}`, { method: 'GET' });
175
187
  return {
176
- active: body.claims ?? [],
177
- queue: body.queue ?? [],
188
+ active: (body.claims ?? []).map(healClaimPhase),
189
+ queue: (body.queue ?? []).map(healClaimPhase),
178
190
  };
179
191
  }
180
192
  function delay(ms, signal) {
@@ -374,7 +386,8 @@ export function createProtocolClient(options) {
374
386
  body: JSON.stringify({
375
387
  claimId,
376
388
  target: claimOptions.target,
377
- action: claimOptions.action,
389
+ // Wire field stays `action`; public option is `reason`.
390
+ action: claimOptions.reason,
378
391
  ttl: claimOptions.ttl,
379
392
  queue: claimOptions.queue,
380
393
  }),
@@ -400,7 +413,7 @@ export function createProtocolClient(options) {
400
413
  return {
401
414
  object: 'claim',
402
415
  claimId: id,
403
- action: claimOptions.action,
416
+ reason: claimOptions.reason,
404
417
  target: claimOptions.target,
405
418
  release,
406
419
  revoke: () => {
@@ -451,7 +464,7 @@ export function createProtocolClient(options) {
451
464
  return {
452
465
  data,
453
466
  stamp: query.stamp ?? 0,
454
- claims: query.claims ?? [],
467
+ claims: (query.claims ?? []).map(healClaimPhase),
455
468
  };
456
469
  }
457
470
  /**
@@ -533,13 +546,14 @@ export function createProtocolClient(options) {
533
546
  const body = await requestJson(claimPath(params.id), {
534
547
  method: 'POST',
535
548
  body: JSON.stringify({
536
- action: params.action ?? 'editing',
549
+ // Wire field stays `action`; public option is `reason`.
550
+ action: params.reason ?? 'editing',
537
551
  ...(params.ttl !== undefined ? { ttl: params.ttl } : {}),
538
552
  ...(params.description !== undefined ? { description: params.description } : {}),
539
553
  ...(claimMeta(params) ? { meta: claimMeta(params) } : {}),
540
- // `wait` (default true) → queue behind the holder; false → fail-fast
554
+ // `queue` (default true) → queue behind the holder; false → fail-fast
541
555
  // with AbloClaimedError (work-distribution dedup).
542
- queue: params.wait ?? true,
556
+ queue: params.queue ?? true,
543
557
  }),
544
558
  });
545
559
  if (body.status === 'queued') {
@@ -565,7 +579,7 @@ export function createProtocolClient(options) {
565
579
  ...(params.range ? { range: params.range } : {}),
566
580
  ...(claimMeta(params) ? { meta: claimMeta(params) } : {}),
567
581
  },
568
- action: params.action ?? 'editing',
582
+ reason: params.reason ?? 'editing',
569
583
  ...(params.description ? { description: params.description } : {}),
570
584
  data,
571
585
  release,
@@ -580,11 +594,12 @@ export function createProtocolClient(options) {
580
594
  release: releaseClaim,
581
595
  state: async (params) => {
582
596
  const res = await claimsForEntity(params);
583
- return res.claims?.[0] ?? null;
597
+ const first = res.claims?.[0];
598
+ return first ? healClaimPhase(first) : null;
584
599
  },
585
600
  queue: async (params) => {
586
601
  const res = await claimsForEntity(params);
587
- return { object: 'list', data: res.queue ?? [] };
602
+ return { object: 'list', data: (res.queue ?? []).map(healClaimPhase) };
588
603
  },
589
604
  reorder: async (params) => {
590
605
  await requestJson(`${claimPath(params.id)}/reorder`, {
@@ -90,7 +90,8 @@ export interface ModelCollaboration<T> {
90
90
  range?: TargetRange;
91
91
  meta?: Record<string, unknown>;
92
92
  };
93
- action: string;
93
+ /** Human-readable phase (`'editing'`); wire field is `action`. */
94
+ reason: string;
94
95
  ttl?: Duration;
95
96
  /**
96
97
  * Block on the server's fair FIFO queue when the target is held, rather
@@ -178,8 +179,9 @@ export interface ModelCollaboration<T> {
178
179
  createWatch?(modelKey: string, ids: string | readonly string[], options?: WatchOptions): Promise<JoinedParticipant>;
179
180
  }
180
181
  export interface ClaimTargetOptions<T = Record<string, unknown>> {
181
- /** Phase shown to observers while held. Defaults to `'editing'`. */
182
- action?: string;
182
+ /** Human-readable phase shown to observers while held. Defaults to
183
+ * `'editing'`. The same word on every claim surface; wire field is `action`. */
184
+ reason?: string;
183
185
  /** Peer-visible explanation of the work being performed. */
184
186
  description?: string;
185
187
  /** Field-level target, for fine-grained claimed-state badges. */
@@ -199,8 +201,14 @@ export interface ClaimTargetOptions<T = Record<string, unknown>> {
199
201
  * `AbloClaimedError` instead of waiting (claim-or-skip). Use `false` for
200
202
  * work-distribution dedup ("if someone else has this job, skip it") where
201
203
  * waiting would mean double-processing.
204
+ *
205
+ * Named `queue` to match every other claim surface (low-level
206
+ * `claims.claim`, HTTP `claim.create`, and the wire). The high-level typed
207
+ * claim defaults it ON because it serializes writers; the low-level lease
208
+ * and HTTP default it OFF — they return/resolve immediately and can't
209
+ * transparently wait for a grant.
202
210
  */
203
- wait?: boolean;
211
+ queue?: boolean;
204
212
  /**
205
213
  * Backpressure: willing to queue, but not behind too many. If the server
206
214
  * reports `position >= maxQueueDepth` when we join the line, reject with
@@ -226,7 +234,7 @@ export interface ClaimReorderParams<T = Record<string, unknown>> extends ClaimLo
226
234
  * ```ts
227
235
  * const claim = await ablo.weatherReports.claim({
228
236
  * id: 'report_stockholm',
229
- * action: 'forecasting',
237
+ * reason: 'forecasting',
230
238
  * description: 'Fetching current weather before writing the forecast.',
231
239
  * });
232
240
  * try {
@@ -258,7 +266,7 @@ export type ClaimOptions<T = Record<string, unknown>> = ClaimTargetOptions<T>;
258
266
  * data: { title },
259
267
  * claim: {
260
268
  * field: 'title',
261
- * action: 'renaming',
269
+ * reason: 'renaming',
262
270
  * description: 'Renaming the task to match the project brief.',
263
271
  * },
264
272
  * });
@@ -379,7 +387,7 @@ export interface ModelOperations<T, CreateInput> {
379
387
  * ```ts
380
388
  * const claim = await ablo.weatherReports.claim({
381
389
  * id: 'report_stockholm',
382
- * action: 'forecasting',
390
+ * reason: 'forecasting',
383
391
  * description: 'Fetching fresh weather before updating the report.',
384
392
  * });
385
393
  * const weather = await getWeather(claim.data.location);
@@ -77,7 +77,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
77
77
  // `release({ id })` and `update({ id, data })` find the lease + snapshot a `claim({ id })`
78
78
  // took — no per-call handle. Released on dispose, explicit release, or TTL.
79
79
  //
80
- // `target` / `action` / `expiresAt` are kept alongside the lease so
80
+ // `target` / `reason` / `expiresAt` are kept alongside the lease so
81
81
  // `claim.state` can synthesize a self-claim: the server excludes a holder's
82
82
  // own presence frames, so the local proxy is the ONLY place that knows "I
83
83
  // hold this." `expiresAt` is the client's best estimate from the requested
@@ -103,7 +103,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
103
103
  id: claim.id,
104
104
  actor: claim.heldBy,
105
105
  participantKind: claim.participantKind,
106
- action: claim.action,
106
+ reason: claim.reason,
107
107
  ...(description ? { description } : {}),
108
108
  field: claim.target.field,
109
109
  status: claim.status,
@@ -143,8 +143,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
143
143
  // claim (a free / already-mine target can't have changed under us).
144
144
  const held = collaboration.observe({ model: wireModel, id });
145
145
  const contended = !!held && held.heldBy !== collaboration.selfParticipantId;
146
- const failFast = options?.wait === false;
147
- // Fail-fast (`wait: false`): if another participant already holds it,
146
+ const failFast = options?.queue === false;
147
+ // Fail-fast (`queue: false`): if another participant already holds it,
148
148
  // reject now instead of queuing. Best-effort at the client (a racing
149
149
  // claim not yet synced into our snapshot slips through here) — the
150
150
  // commit-time claim guard is the authoritative backstop that rejects
@@ -176,7 +176,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
176
176
  // in the group when `createClaim` lands. Awaited because the broadcast
177
177
  // ordering depends on it; still soft (the store swallows reconcile errors).
178
178
  await collaboration.pinScope?.({ [schemaKey]: id });
179
- // Acquire the lease. Default (`wait` !== false) goes through the server's
179
+ // Acquire the lease. Default (`queue` !== false) goes through the server's
180
180
  // fair FIFO queue — `queue: true` resolves only once the lease is genuinely
181
181
  // ours, blocking behind any current holder, with no TOCTOU gap (the server
182
182
  // orders contenders). Fail-fast skips the queue: we already rejected an
@@ -190,7 +190,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
190
190
  ...(options?.range ? { range: options.range } : {}),
191
191
  ...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
192
192
  },
193
- action: options?.action ?? 'editing',
193
+ reason: options?.reason ?? 'editing',
194
194
  ttl: options?.ttl,
195
195
  queue: !failFast,
196
196
  maxQueueDepth: options?.maxQueueDepth,
@@ -213,7 +213,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
213
213
  model = objectPool.get(id) ?? model;
214
214
  }
215
215
  const snapshot = collaboration.createSnapshot(schemaKey, id);
216
- const action = options?.action ?? 'editing';
216
+ const reason = options?.reason ?? 'editing';
217
217
  // The self-claim's `EntityRef` mirrors what a peer's `claim.state` would
218
218
  // report (`observe` maps `held.target.model` → `type`), so a holder and a
219
219
  // peer see the SAME target.type for one row — the wire model token.
@@ -231,7 +231,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
231
231
  lease,
232
232
  snapshot,
233
233
  target: selfTarget,
234
- action,
234
+ reason,
235
235
  expiresAt,
236
236
  });
237
237
  const target = {
@@ -248,7 +248,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
248
248
  claimId: lease.claimId,
249
249
  readAt: snapshot.stamp,
250
250
  target,
251
- action,
251
+ reason,
252
252
  ...(options?.description ? { description: options.description } : {}),
253
253
  data: modelAsRow(model),
254
254
  release,
@@ -281,7 +281,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
281
281
  id: own.lease.claimId,
282
282
  status: 'active',
283
283
  target: own.target,
284
- action: own.action,
284
+ reason: own.reason,
285
285
  heldBy: collaboration?.selfParticipantId ?? '',
286
286
  participantKind: collaboration?.selfParticipantKind ?? 'user',
287
287
  expiresAt: own.expiresAt,
@@ -381,9 +381,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
381
381
  ...(claim.range ? { range: claim.range } : {}),
382
382
  ...(claimMeta(claim) ? { meta: claimMeta(claim) } : {}),
383
383
  },
384
- action: claim.action ?? 'creating',
384
+ reason: claim.reason ?? 'creating',
385
385
  ttl: claim.ttl,
386
- queue: claim.wait !== false,
386
+ queue: claim.queue !== false,
387
387
  maxQueueDepth: claim.maxQueueDepth,
388
388
  });
389
389
  }
@@ -281,7 +281,7 @@ export declare const modelClaimSchema: z.ZodReadonly<z.ZodObject<{
281
281
  agent: "agent";
282
282
  system: "system";
283
283
  }>>;
284
- action: z.ZodString;
284
+ reason: z.ZodString;
285
285
  description: z.ZodOptional<z.ZodString>;
286
286
  field: z.ZodOptional<z.ZodString>;
287
287
  status: z.ZodOptional<z.ZodEnum<{
@@ -203,7 +203,9 @@ export const modelClaimSchema = z
203
203
  id: z.string(),
204
204
  actor: z.string(),
205
205
  participantKind: wireParticipantKindSchema,
206
- action: z.string(),
206
+ /** Human-readable phase (`'editing'`). The public SDK field; the WS/HTTP
207
+ * wire carries the same value as `action` (healed on read). */
208
+ reason: z.string(),
207
209
  description: z.string().optional(),
208
210
  field: z.string().optional(),
209
211
  status: z.enum(['active', 'queued']).optional(),
package/dist/errors.d.ts CHANGED
@@ -161,7 +161,9 @@ export interface ClaimContext {
161
161
  readonly claimId?: string;
162
162
  readonly actor?: string;
163
163
  readonly participantKind?: ParticipantKind;
164
- readonly action?: string;
164
+ /** Human-readable phase the holder is in (`'editing'`). Matches the public
165
+ * claim surface; the wire summary carries the same value as `action`. */
166
+ readonly reason?: string;
165
167
  readonly description?: string;
166
168
  readonly field?: string;
167
169
  readonly status?: string;
package/dist/errors.js CHANGED
@@ -162,7 +162,12 @@ export class AbloStaleContextError extends AbloError {
162
162
  }
163
163
  }
164
164
  function claimAction(claim) {
165
- return claim?.action;
165
+ if (!claim)
166
+ return undefined;
167
+ // The public `ClaimContext` exposes the phase as `reason`; the wire
168
+ // `WireClaimSummary` projection still carries it under `action`. Read both.
169
+ const c = claim;
170
+ return c.reason ?? c.action;
166
171
  }
167
172
  function claimDescription(claim) {
168
173
  if (!claim)
@@ -15,7 +15,7 @@ import { type SyncStoreContract } from './context.js';
15
15
  * - **One component, one import.** Consumers write the provider
16
16
  * once at the root; nothing else needs to plumb the engine.
17
17
  * - **Multiplayer is default.** React consumers are always browsers doing
18
- * multiplayer UI, so `useParticipant()` / `useAblo()` are always
18
+ * multiplayer UI, so `useWatch()` / `useAblo()` are always
19
19
  * available. No opt-in prop.
20
20
  * - **Declarative props for app glue.** `preventUnsavedChanges`,
21
21
  * `onSessionExpired`, `postBootstrap`, `resolveUsers` — each
@@ -114,11 +114,11 @@ export interface AbloProviderProps<R extends SchemaRecord = SchemaRecord> {
114
114
  export declare function AbloProvider<R extends SchemaRecord = SchemaRecord>(props: AbloProviderProps<R>): React.ReactElement;
115
115
  export type { EngineParticipant, ParticipantScope, ParticipantStatus };
116
116
  /**
117
- * Options for `useParticipant`. The hook reuses the engine's single
117
+ * Options for `useWatch`. The hook reuses the engine's single
118
118
  * WebSocket and opens a scoped claim on it when `scope` is provided:
119
119
  * one TCP connection, N logical sub-syncgroup participants.
120
120
  */
121
- export interface UseParticipantOptions {
121
+ export interface UseWatchOptions {
122
122
  readonly scope?: ParticipantScope;
123
123
  readonly ttlSeconds?: number | string | null;
124
124
  /** Tear down + don't re-join while true. */
@@ -149,7 +149,7 @@ export interface UseParticipantOptions {
149
149
  }
150
150
  /** @deprecated Use `ParticipantStatus`. */
151
151
  export type MeshParticipantStatus = ParticipantStatus;
152
- export interface UseParticipantReturn {
152
+ export interface UseWatchReturn {
153
153
  readonly participant: EngineParticipant | null;
154
154
  /** Everyone else on the engine's sync groups (`participant.presence.others`), bridged to React. */
155
155
  readonly peers: ReadonlyArray<Peer>;
@@ -163,15 +163,19 @@ export interface UseParticipantReturn {
163
163
  * lifecycle status. Auto-cleans up on unmount or when `paused`
164
164
  * flips to true.
165
165
  *
166
+ * `useWatch` is the React form of `ablo.<model>.watch` — scope-level
167
+ * read-interest + presence; returns the reactive participant facade
168
+ * (peers/claims/status).
169
+ *
166
170
  * The returned `participant` is an `EngineParticipant` — `.presence`
167
171
  * + `.claims` only — backed by the engine's existing socket. For
168
172
  * headless-bot patterns (a separate identity in the same browser
169
173
  * tab), construct a second `Ablo({ kind: 'agent', ... })` directly.
170
174
  */
171
- export declare function useParticipant(opts: UseParticipantOptions): UseParticipantReturn;
175
+ export declare function useWatch(opts: UseWatchOptions): UseWatchReturn;
172
176
  /**
173
177
  * Read-only presence: the OTHER participants currently visible to this
174
- * connection, bridged to React. Unlike {@link useParticipant}, this does
178
+ * connection, bridged to React. Unlike {@link useWatch}, this does
175
179
  * NOT enter/leave a scope (no `update_subscription`, no warm-TTL churn) —
176
180
  * it is a pure reader of the engine's already-flowing presence stream.
177
181
  *
@@ -184,7 +188,7 @@ export declare function useParticipant(opts: UseParticipantOptions): UseParticip
184
188
  * Use this to answer "is anyone else here?" — e.g. suppressing live-cursor
185
189
  * broadcasts while alone — when some OTHER mount already owns the scope's
186
190
  * read interest (scope `leave` is not reference-counted, so a second
187
- * `useParticipant` on the same scope would warm-drop the owner's
191
+ * `useWatch` on the same scope would warm-drop the owner's
188
192
  * subscription on unmount).
189
193
  *
190
194
  * ```ts
@@ -204,12 +204,16 @@ const EMPTY_INTENTS = Object.freeze([]);
204
204
  * lifecycle status. Auto-cleans up on unmount or when `paused`
205
205
  * flips to true.
206
206
  *
207
+ * `useWatch` is the React form of `ablo.<model>.watch` — scope-level
208
+ * read-interest + presence; returns the reactive participant facade
209
+ * (peers/claims/status).
210
+ *
207
211
  * The returned `participant` is an `EngineParticipant` — `.presence`
208
212
  * + `.claims` only — backed by the engine's existing socket. For
209
213
  * headless-bot patterns (a separate identity in the same browser
210
214
  * tab), construct a second `Ablo({ kind: 'agent', ... })` directly.
211
215
  */
212
- export function useParticipant(opts) {
216
+ export function useWatch(opts) {
213
217
  const ctx = useContext(AbloInternalContext);
214
218
  const engine = ctx?.engine ?? null;
215
219
  const { paused = false } = opts;
@@ -342,7 +346,7 @@ export function useParticipant(opts) {
342
346
  }
343
347
  /**
344
348
  * Read-only presence: the OTHER participants currently visible to this
345
- * connection, bridged to React. Unlike {@link useParticipant}, this does
349
+ * connection, bridged to React. Unlike {@link useWatch}, this does
346
350
  * NOT enter/leave a scope (no `update_subscription`, no warm-TTL churn) —
347
351
  * it is a pure reader of the engine's already-flowing presence stream.
348
352
  *
@@ -355,7 +359,7 @@ export function useParticipant(opts) {
355
359
  * Use this to answer "is anyone else here?" — e.g. suppressing live-cursor
356
360
  * broadcasts while alone — when some OTHER mount already owns the scope's
357
361
  * read interest (scope `leave` is not reference-counted, so a second
358
- * `useParticipant` on the same scope would warm-drop the owner's
362
+ * `useWatch` on the same scope would warm-drop the owner's
359
363
  * subscription on unmount).
360
364
  *
361
365
  * ```ts
@@ -366,7 +370,7 @@ export function useParticipant(opts) {
366
370
  export function usePeers(scope) {
367
371
  const ctx = useContext(AbloInternalContext);
368
372
  const engine = ctx?.engine ?? null;
369
- // Resolve scope → groups through the schema (same idiom as useParticipant).
373
+ // Resolve scope → groups through the schema (same idiom as useWatch).
370
374
  // The stringified, sorted key is the stable effect dependency.
371
375
  const scopeKey = JSON.stringify(resolveParticipantSyncGroups(scope, engine?.schema).sort());
372
376
  const groups = useMemo(() => JSON.parse(scopeKey), [scopeKey]);
@@ -383,7 +387,7 @@ export function usePeers(scope) {
383
387
  // Plain useState + onChange — presence changes on join/leave/activity
384
388
  // only (never on cursor traffic, a separate channel), so this fires
385
389
  // rarely; a frame of stale presence is harmless (same rationale as
386
- // useParticipant's peers bridge).
390
+ // useWatch's peers bridge).
387
391
  setPeers(compute());
388
392
  return presence.onChange(() => setPeers(compute()));
389
393
  }, [engine, scopeKey]);
@@ -140,8 +140,9 @@ export interface SyncReactContext {
140
140
  }
141
141
  export declare const SyncContext: import("react").Context<SyncReactContext | null>;
142
142
  /**
143
- * Access the sync store from React components.
144
- * Must be used within a SyncProvider.
143
+ * Access the sync store from React components. The context is provided by
144
+ * `<AbloProvider>` (which renders the internal {@link SyncProvider}); public
145
+ * consumers wire `<AbloProvider client={ablo}>`, never this directly.
145
146
  */
146
147
  export declare function useSyncContext(): SyncReactContext;
147
148
  /**
@@ -162,18 +163,12 @@ export interface SyncProviderProps {
162
163
  children?: ReactNode;
163
164
  }
164
165
  /**
165
- * SyncProvider wires the sync store into React so SDK hooks
166
- * (useModel, useModels, useMutations) can access it.
166
+ * SyncProvider the INTERNAL low-level provider that wires a built sync store
167
+ * into React so SDK hooks (useModel, useModels, useMutations) can reach it.
167
168
  *
168
- * @example
169
- * import { SyncProvider } from '@abloatai/ablo/react';
170
- *
171
- * function App() {
172
- * return (
173
- * <SyncProvider store={syncStore} organizationId={orgId}>
174
- * <YourApp />
175
- * </SyncProvider>
176
- * );
177
- * }
169
+ * Public consumers do NOT use this directly (it is not exported from
170
+ * `@abloatai/ablo/react`). `<AbloProvider client={ablo}>` constructs the
171
+ * store from your `Ablo({ schema, apiKey })` client and renders this provider
172
+ * underneath reach for `<AbloProvider>`.
178
173
  */
179
174
  export declare function SyncProvider({ store, organizationId, schema, children, }: SyncProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SyncReactContext | null>>;
@@ -3,32 +3,27 @@ import { createContext, createElement, useContext } from 'react';
3
3
  import { AbloValidationError } from '../errors.js';
4
4
  export const SyncContext = createContext(null);
5
5
  /**
6
- * Access the sync store from React components.
7
- * Must be used within a SyncProvider.
6
+ * Access the sync store from React components. The context is provided by
7
+ * `<AbloProvider>` (which renders the internal {@link SyncProvider}); public
8
+ * consumers wire `<AbloProvider client={ablo}>`, never this directly.
8
9
  */
9
10
  export function useSyncContext() {
10
11
  const ctx = useContext(SyncContext);
11
12
  if (!ctx) {
12
- throw new AbloValidationError('useSyncContext must be used within a SyncProvider', {
13
+ throw new AbloValidationError('Sync hooks must be used within an <AbloProvider>.', {
13
14
  code: 'sync_context_missing_provider',
14
15
  });
15
16
  }
16
17
  return ctx;
17
18
  }
18
19
  /**
19
- * SyncProvider wires the sync store into React so SDK hooks
20
- * (useModel, useModels, useMutations) can access it.
20
+ * SyncProvider the INTERNAL low-level provider that wires a built sync store
21
+ * into React so SDK hooks (useModel, useModels, useMutations) can reach it.
21
22
  *
22
- * @example
23
- * import { SyncProvider } from '@abloatai/ablo/react';
24
- *
25
- * function App() {
26
- * return (
27
- * <SyncProvider store={syncStore} organizationId={orgId}>
28
- * <YourApp />
29
- * </SyncProvider>
30
- * );
31
- * }
23
+ * Public consumers do NOT use this directly (it is not exported from
24
+ * `@abloatai/ablo/react`). `<AbloProvider client={ablo}>` constructs the
25
+ * store from your `Ablo({ schema, apiKey })` client and renders this provider
26
+ * underneath reach for `<AbloProvider>`.
32
27
  */
33
28
  export function SyncProvider({ store, organizationId, schema, children, }) {
34
29
  return createElement(SyncContext.Provider, { value: { store, organizationId, schema } }, children);
@@ -2,8 +2,12 @@
2
2
  * @abloatai/ablo/react — React bindings (v0.3.0)
3
3
  *
4
4
  * Umbrella provider:
5
- * <AbloProvider schema={...} userId={...} orgId={...} fallback={<Skeleton/>}>
6
- * owns sync engine + multiplayer lifecycle; the `fallback` prop
5
+ * const ablo = Ablo({ schema, apiKey }) // build once — module scope or useMemo
6
+ * <AbloProvider client={ablo} fallback={<Skeleton/>}>
7
+ * — `client` is the only required prop (construct it yourself; the provider
8
+ * is the thin reactive binding, like `<Elements stripe={...}>`). `userId`
9
+ * is optional + informational. Owns sync engine + multiplayer lifecycle;
10
+ * the `fallback` prop
7
11
  * gates children on first bootstrap. Pass `fallback="passthrough"`
8
12
  * to disable the gate.
9
13
  * <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
@@ -27,7 +31,7 @@
27
31
  *
28
32
  * Multiplayer (always available — `<AbloProvider>` always constructs a client):
29
33
  * useAblo((ablo) => ablo.<model>.claim.state(...)) — reactive coordination reads
30
- * useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
34
+ * useWatch({ scope }) — join multiplayer for a scope, get peers/claims
31
35
  *
32
36
  * ── Breaking changes from v0.2.x ───────────────────────────────────
33
37
  * Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
@@ -41,7 +45,7 @@
41
45
  * migration notes in CHANGELOG.md.
42
46
  */
43
47
  export type { DefaultSyncShape, ResolveSchema, ResolvePresence, ResolveClaims, ResolveUserMeta, ResolveModelKey, } from '../types/global.js';
44
- export { AbloProvider, useParticipant, usePeers, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseParticipantOptions, type UseParticipantReturn, type MeshParticipantStatus, } from './AbloProvider.js';
48
+ export { AbloProvider, useWatch, usePeers, useSync, useSyncStore, type AbloProviderProps, type ParticipantScope, type ParticipantStatus, type UseWatchOptions, type UseWatchReturn, type MeshParticipantStatus, } from './AbloProvider.js';
45
49
  export { ClientSideSuspense, type ClientSideSuspenseProps, } from './ClientSideSuspense.js';
46
50
  export { DefaultFallback } from './DefaultFallback.js';
47
51
  export type { SyncStoreContract } from './context.js';
@@ -2,8 +2,12 @@
2
2
  * @abloatai/ablo/react — React bindings (v0.3.0)
3
3
  *
4
4
  * Umbrella provider:
5
- * <AbloProvider schema={...} userId={...} orgId={...} fallback={<Skeleton/>}>
6
- * owns sync engine + multiplayer lifecycle; the `fallback` prop
5
+ * const ablo = Ablo({ schema, apiKey }) // build once — module scope or useMemo
6
+ * <AbloProvider client={ablo} fallback={<Skeleton/>}>
7
+ * — `client` is the only required prop (construct it yourself; the provider
8
+ * is the thin reactive binding, like `<Elements stripe={...}>`). `userId`
9
+ * is optional + informational. Owns sync engine + multiplayer lifecycle;
10
+ * the `fallback` prop
7
11
  * gates children on first bootstrap. Pass `fallback="passthrough"`
8
12
  * to disable the gate.
9
13
  * <ClientSideSuspense fallback={<Skeleton/>}> — NESTED gate inside an
@@ -27,7 +31,7 @@
27
31
  *
28
32
  * Multiplayer (always available — `<AbloProvider>` always constructs a client):
29
33
  * useAblo((ablo) => ablo.<model>.claim.state(...)) — reactive coordination reads
30
- * useParticipant({ scope }) — join multiplayer for a scope, get peers/claims
34
+ * useWatch({ scope }) — join multiplayer for a scope, get peers/claims
31
35
  *
32
36
  * ── Breaking changes from v0.2.x ───────────────────────────────────
33
37
  * Removed: <SyncProvider>, SyncContext, useSyncContext — folded into
@@ -41,7 +45,7 @@
41
45
  * migration notes in CHANGELOG.md.
42
46
  */
43
47
  // ── Umbrella provider + lifecycle hooks ────────────────────────────
44
- export { AbloProvider, useParticipant, usePeers, useSync, useSyncStore, } from './AbloProvider.js';
48
+ export { AbloProvider, useWatch, usePeers, useSync, useSyncStore, } from './AbloProvider.js';
45
49
  export { ClientSideSuspense, } from './ClientSideSuspense.js';
46
50
  export { DefaultFallback } from './DefaultFallback.js';
47
51
  // ── Status + errors + identity ─────────────────────────────────────
@@ -17,8 +17,9 @@ export function useMutators(schemaOrMutators, mutatorsOrOptions, maybeOptions) {
17
17
  const mutators = (isExplicit ? mutatorsOrOptions : schemaOrMutators);
18
18
  const options = (isExplicit ? maybeOptions : mutatorsOrOptions);
19
19
  if (!schema) {
20
- throw new AbloValidationError('useMutators: no schema available. Pass the schema as the first arg ' +
21
- 'or wire SyncProvider with a `schema` prop when using the zero-arg overload.', { code: 'mutators_schema_missing' });
20
+ throw new AbloValidationError('useMutators: no schema available. Pass the schema as the first arg, ' +
21
+ 'or build the <AbloProvider> above with `Ablo({ schema })` so the ' +
22
+ 'zero-arg overload can read it from context.', { code: 'mutators_schema_missing' });
22
23
  }
23
24
  const { undoScope } = options ?? {};
24
25
  return useMemo(() => {
@@ -34,8 +34,9 @@ export function useUndoScope(schemaOrName, nameOrOptions, maybeOptions) {
34
34
  const name = isExplicit ? nameOrOptions : schemaOrName;
35
35
  const options = (isExplicit ? maybeOptions : nameOrOptions);
36
36
  if (!schema) {
37
- throw new AbloValidationError('useUndoScope: no schema available. Pass the schema as the first arg ' +
38
- 'or wire SyncProvider with a `schema` prop when using the zero-arg overload.', { code: 'undo_scope_schema_missing' });
37
+ throw new AbloValidationError('useUndoScope: no schema available. Pass the schema as the first arg, ' +
38
+ 'or build the <AbloProvider> above with `Ablo({ schema })` so the ' +
39
+ 'zero-arg overload can read it from context.', { code: 'undo_scope_schema_missing' });
39
40
  }
40
41
  const scope = useMemo(() => {
41
42
  // Store is the identity for the manager — one per SyncProvider.
@@ -95,9 +95,16 @@ export interface ModelOptions {
95
95
  */
96
96
  tableName?: string;
97
97
  /**
98
- * Whether this model's table has an organization_id column.
99
- * Default: true. When false, the bootstrap query omits the
100
- * `WHERE organization_id = $1` clause for this model.
98
+ * Whether this model's table has an `organization_id` column. Default: true.
99
+ * When false, the bootstrap/read query omits the `WHERE organization_id = $1`
100
+ * tenant filter for this model.
101
+ *
102
+ * ⚠ SECURITY — `orgScoped: false` makes the table GLOBALLY READABLE: every
103
+ * client of every tenant sees every row. It is ONLY correct for genuinely
104
+ * tenant-less tables (the `organizations` table itself, global lookups). If
105
+ * rows belong to a tenant through a foreign key but this table has no
106
+ * `organization_id` of its own, use {@link scopedVia} INSTEAD — reaching for
107
+ * `orgScoped: false` there silently exposes the entire table cross-tenant.
101
108
  */
102
109
  orgScoped?: boolean;
103
110
  /**
@@ -192,7 +192,8 @@ export function createClaimStream(config, transport = null) {
192
192
  entityId: claim.entityId,
193
193
  path: claim.path,
194
194
  range: claim.range,
195
- action: claim.action,
195
+ // Wire field stays `action` (coordination schema); source is `reason`.
196
+ action: claim.reason,
196
197
  field: claim.field,
197
198
  meta: claim.meta,
198
199
  estimatedMs: claim.estimatedMs,
@@ -244,7 +245,7 @@ export function createClaimStream(config, transport = null) {
244
245
  range: args.range,
245
246
  field: args.field,
246
247
  meta: args.meta,
247
- action: args.action,
248
+ reason: args.reason,
248
249
  estimatedMs,
249
250
  queue: args.queue,
250
251
  };
@@ -261,7 +262,7 @@ export function createClaimStream(config, transport = null) {
261
262
  return {
262
263
  object: 'claim',
263
264
  claimId,
264
- action: args.action,
265
+ reason: args.reason,
265
266
  target: {
266
267
  model: args.entityType,
267
268
  id: args.entityId,
@@ -294,7 +295,7 @@ export function createClaimStream(config, transport = null) {
294
295
  range: resolved.range,
295
296
  field: resolved.field,
296
297
  meta: withDescription(resolved.meta, opts?.description),
297
- action: opts?.reason ?? 'editing',
298
+ reason: opts?.reason ?? 'editing',
298
299
  ttl: opts?.ttl,
299
300
  queue: opts?.queue,
300
301
  });
@@ -221,7 +221,7 @@ function createJoinedParticipant(args) {
221
221
  return {
222
222
  object: 'claim',
223
223
  claimId: handle.claimId,
224
- action: handle.action,
224
+ reason: handle.reason,
225
225
  target: handle.target,
226
226
  async release() {
227
227
  ownHandles.delete(handle);
@@ -360,7 +360,7 @@ export interface ClaimStream {
360
360
  /**
361
361
  * Reactive view of the wait queue on one target — the FIFO line of
362
362
  * `status: 'queued'` claims behind the current holder, each with its
363
- * `action`, `heldBy`, and `position`. Synced from the server's per-entity
363
+ * `reason`, `heldBy`, and `position`. Synced from the server's per-entity
364
364
  * `claim_queue` frame; empty when no one's waiting. Pair with
365
365
  * `subscribe(...)` for change notifications.
366
366
  */
@@ -457,10 +457,12 @@ export interface ClaimDeclaration {
457
457
  /** Human-readable reason — "rewriting title" / "restyling chart". */
458
458
  readonly reason: string;
459
459
  /**
460
- * Expiry auto-revoke if the participant doesn't finish in time.
461
- * Number = seconds (back-compat); string = duration (`'3m'`).
460
+ * Seconds remaining until the server auto-expires this claim. An OUTPUT
461
+ * field carrying a concrete countdown, so it's a plain `number` — distinct
462
+ * from the input `ttl: Duration` (`'3m'`) you pass when announcing. Computed
463
+ * from `expiresAt - now`.
462
464
  */
463
- readonly ttlSeconds?: Duration;
465
+ readonly ttlSeconds?: number;
464
466
  }
465
467
  /**
466
468
  * Handle returned from `announce(...)` / `analyzing(...)` / etc.
@@ -507,7 +509,13 @@ export interface ClaimHandle<T = Record<string, unknown>> extends AsyncDisposabl
507
509
  readonly range?: TargetRange;
508
510
  readonly meta?: Record<string, unknown>;
509
511
  };
510
- readonly action: string;
512
+ /**
513
+ * The human-readable phase this claim represents — `'editing'`, `'writing'`,
514
+ * `'forecasting'`. The SAME word on every claim surface (inputs and outputs);
515
+ * distinct from the CRUD operation (`CommitOperationInput.action`). Defaults
516
+ * to `'editing'`. Serialized on the wire as `action`.
517
+ */
518
+ readonly reason: string;
511
519
  readonly description?: string;
512
520
  /** Row snapshot — populated by `ablo.<model>.claim`; absent on low-level leases. */
513
521
  readonly data?: T;
@@ -565,8 +573,10 @@ export interface Claim {
565
573
  readonly status: ClaimStatus;
566
574
  /** What is being coordinated. */
567
575
  readonly target: EntityRef;
568
- /** Human-readable phase — `'editing'`, `'writing'`, `'reviewing'`. */
569
- readonly action: string;
576
+ /** Human-readable phase — `'editing'`, `'writing'`, `'reviewing'`. The same
577
+ * field on every claim surface; distinct from the CRUD operation. Serialized
578
+ * on the wire as `action`. */
579
+ readonly reason: string;
570
580
  /** Peer-visible explanation of the work being performed. */
571
581
  readonly description?: string;
572
582
  /** Participant holding it. */
package/docs/migration.md CHANGED
@@ -284,7 +284,8 @@ One provider component now owns the full React lifecycle. `<SyncProvider>`,
284
284
  - <SyncProvider store={sync._store} organizationId={orgId}>
285
285
  - <AbloProvider ablo={ablo}>{children}</AbloProvider>
286
286
  - </SyncProvider>
287
- + <AbloProvider schema={schema} url={url} userId={userId} organizationId={orgId}>
287
+ + const ablo = Ablo({ schema, apiKey });
288
+ + <AbloProvider client={ablo}>
288
289
  + {children}
289
290
  + </AbloProvider>
290
291
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "The Collaboration Layer For AI Agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",