@abloatai/ablo 0.10.0 → 0.11.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 (94) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +2 -1
  3. package/dist/BaseSyncedStore.d.ts +75 -0
  4. package/dist/BaseSyncedStore.js +193 -8
  5. package/dist/Database.d.ts +10 -2
  6. package/dist/Database.js +15 -1
  7. package/dist/SyncClient.d.ts +12 -1
  8. package/dist/SyncClient.js +110 -26
  9. package/dist/agent/Agent.d.ts +9 -9
  10. package/dist/agent/Agent.js +16 -16
  11. package/dist/agent/index.d.ts +1 -1
  12. package/dist/agent/index.js +2 -2
  13. package/dist/agent/types.d.ts +1 -1
  14. package/dist/agent/types.js +1 -1
  15. package/dist/ai-sdk/{intent-broadcast.d.ts → claim-broadcast.d.ts} +10 -10
  16. package/dist/ai-sdk/{intent-broadcast.js → claim-broadcast.js} +6 -6
  17. package/dist/ai-sdk/coordination-context.d.ts +9 -9
  18. package/dist/ai-sdk/coordination-context.js +8 -8
  19. package/dist/ai-sdk/index.d.ts +1 -1
  20. package/dist/ai-sdk/index.js +1 -1
  21. package/dist/ai-sdk/wrap.d.ts +4 -4
  22. package/dist/ai-sdk/wrap.js +4 -4
  23. package/dist/api/index.d.ts +2 -2
  24. package/dist/cli.cjs +254 -48
  25. package/dist/client/Ablo.d.ts +30 -63
  26. package/dist/client/Ablo.js +108 -102
  27. package/dist/client/ApiClient.d.ts +6 -5
  28. package/dist/client/ApiClient.js +83 -62
  29. package/dist/client/createModelProxy.d.ts +16 -54
  30. package/dist/client/createModelProxy.js +44 -16
  31. package/dist/client/httpClient.d.ts +2 -0
  32. package/dist/client/httpClient.js +1 -1
  33. package/dist/client/index.d.ts +3 -3
  34. package/dist/client/writeOptionsSchema.d.ts +4 -4
  35. package/dist/client/writeOptionsSchema.js +4 -4
  36. package/dist/coordination/schema.d.ts +249 -38
  37. package/dist/coordination/schema.js +172 -39
  38. package/dist/core/index.d.ts +2 -2
  39. package/dist/core/index.js +4 -4
  40. package/dist/errorCodes.d.ts +9 -9
  41. package/dist/errorCodes.js +15 -15
  42. package/dist/errors.d.ts +51 -2
  43. package/dist/errors.js +94 -5
  44. package/dist/interfaces/index.d.ts +8 -4
  45. package/dist/policy/index.d.ts +1 -1
  46. package/dist/policy/types.d.ts +13 -13
  47. package/dist/policy/types.js +8 -8
  48. package/dist/react/AbloProvider.d.ts +51 -4
  49. package/dist/react/AbloProvider.js +95 -11
  50. package/dist/react/context.d.ts +26 -9
  51. package/dist/react/context.js +2 -2
  52. package/dist/react/index.d.ts +4 -4
  53. package/dist/react/index.js +4 -4
  54. package/dist/react/useAblo.js +5 -5
  55. package/dist/react/{useIntent.d.ts → useClaim.d.ts} +9 -9
  56. package/dist/react/useClaim.js +42 -0
  57. package/dist/schema/index.js +1 -1
  58. package/dist/schema/sugar.d.ts +3 -3
  59. package/dist/schema/sugar.js +3 -3
  60. package/dist/schema/sync-delta-wire.d.ts +8 -8
  61. package/dist/server/commit.d.ts +2 -2
  62. package/dist/sync/AreaOfInterestManager.d.ts +162 -0
  63. package/dist/sync/AreaOfInterestManager.js +233 -0
  64. package/dist/sync/BootstrapHelper.d.ts +9 -1
  65. package/dist/sync/BootstrapHelper.js +15 -5
  66. package/dist/sync/NetworkProbe.d.ts +1 -1
  67. package/dist/sync/NetworkProbe.js +1 -1
  68. package/dist/sync/SyncWebSocket.d.ts +59 -25
  69. package/dist/sync/SyncWebSocket.js +123 -26
  70. package/dist/sync/awaitClaimGrant.d.ts +40 -0
  71. package/dist/sync/awaitClaimGrant.js +86 -0
  72. package/dist/sync/createClaimStream.d.ts +34 -0
  73. package/dist/sync/{createIntentStream.js → createClaimStream.js} +92 -81
  74. package/dist/sync/createPresenceStream.js +3 -2
  75. package/dist/sync/participants.d.ts +10 -10
  76. package/dist/sync/participants.js +17 -10
  77. package/dist/sync/schemas.d.ts +8 -8
  78. package/dist/transactions/TransactionQueue.d.ts +12 -0
  79. package/dist/transactions/TransactionQueue.js +126 -8
  80. package/dist/types/global.d.ts +10 -10
  81. package/dist/types/global.js +3 -3
  82. package/dist/types/index.d.ts +9 -7
  83. package/dist/types/index.js +2 -2
  84. package/dist/types/streams.d.ts +114 -98
  85. package/dist/types/streams.js +1 -1
  86. package/dist/utils/asyncIterator.d.ts +1 -1
  87. package/dist/utils/asyncIterator.js +1 -1
  88. package/dist/wire/frames.d.ts +2 -2
  89. package/docs/migration.md +52 -0
  90. package/package.json +3 -2
  91. package/dist/react/useIntent.js +0 -42
  92. package/dist/sync/awaitIntentGrant.d.ts +0 -40
  93. package/dist/sync/awaitIntentGrant.js +0 -62
  94. package/dist/sync/createIntentStream.d.ts +0 -34
@@ -15,7 +15,8 @@
15
15
  * client assembles the `ablo.<model>` lookup table from these.
16
16
  */
17
17
  import { autorun } from 'mobx';
18
- import { AbloClaimedError, AbloValidationError, toAbloError, } from '../errors.js';
18
+ import { AbloClaimedError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../errors.js';
19
+ import { descriptionFromMeta } from '../coordination/schema.js';
19
20
  import { Model, modelAsRow } from '../Model.js';
20
21
  import { assertWriteOptions } from './writeOptionsSchema.js';
21
22
  import { ModelScope } from '../types/index.js';
@@ -31,9 +32,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
31
32
  throw new AbloValidationError(`Ablo: schema model "${schemaKey}" resolved to "${registeredModelName}", ` +
32
33
  'but no matching constructor was registered.', { code: 'model_not_registered' });
33
34
  }
34
- // The coordination plane (claims/intents) must speak the SAME wire dialect
35
+ // The coordination plane (claims/claims) must speak the SAME wire dialect
35
36
  // as the commit plane: the lowercased TYPENAME (`task`), not the schema key
36
- // (`tasks`). The server's commit-time intent guard probes the lease store
37
+ // (`tasks`). The server's commit-time claim guard probes the lease store
37
38
  // with the commit op's model name; a lease recorded under the schema key
38
39
  // never collides with it — which silently disarmed the guard for every
39
40
  // model whose schema key differs from its typename (i.e. nearly all of
@@ -85,6 +86,27 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
85
86
  return options?.meta;
86
87
  return { ...(options.meta ?? {}), description: options.description };
87
88
  };
89
+ const claimContextFromClaim = (claim) => {
90
+ const description = claim.description ?? descriptionFromMeta(claim.target.meta);
91
+ return {
92
+ id: claim.id,
93
+ actor: claim.heldBy,
94
+ participantKind: claim.participantKind,
95
+ action: claim.action,
96
+ ...(description ? { description } : {}),
97
+ field: claim.target.field,
98
+ status: claim.status,
99
+ expiresAt: claim.expiresAt,
100
+ target: {
101
+ model: claim.target.type,
102
+ id: claim.target.id,
103
+ path: claim.target.path,
104
+ range: claim.target.range,
105
+ field: claim.target.field,
106
+ meta: claim.target.meta,
107
+ },
108
+ };
109
+ };
88
110
  const mutationOptions = (params) => {
89
111
  const { id: _id, data: _data, claim: _claim, ...rest } = params;
90
112
  // THE write-options schema — runtime twin of the compile-time params.
@@ -114,11 +136,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
114
136
  // Fail-fast (`wait: false`): if another participant already holds it,
115
137
  // reject now instead of queuing. Best-effort at the client (a racing
116
138
  // claim not yet synced into our snapshot slips through here) — the
117
- // commit-time intent guard is the authoritative backstop that rejects
139
+ // commit-time claim guard is the authoritative backstop that rejects
118
140
  // the loser's first write. For work-distribution dedup that's exactly
119
141
  // right: don't wait (that would double-process), skip.
120
142
  if (failFast && contended) {
121
- throw new AbloClaimedError(`${registeredModelName}/${id} is held by ${held?.heldBy ?? 'another participant'}.`, { code: 'entity_claimed' });
143
+ const claim = held ? claimContextFromClaim(held) : undefined;
144
+ throw new AbloClaimedError(formatClaimedErrorMessage({
145
+ targetLabel: `${registeredModelName}/${id}`,
146
+ heldBy: held?.heldBy,
147
+ claim,
148
+ fallback: `${registeredModelName}/${id} is held by ${held?.heldBy ?? 'another participant'}.`,
149
+ }), { code: 'entity_claimed', claims: claim ? [claim] : undefined });
122
150
  }
123
151
  // Ensure the row exists locally before claiming.
124
152
  let model = objectPool.get(id);
@@ -134,7 +162,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
134
162
  // ours, blocking behind any current holder, with no TOCTOU gap (the server
135
163
  // orders contenders). Fail-fast skips the queue: we already rejected an
136
164
  // observed conflict above, so this just records our lease.
137
- const lease = await collaboration.createIntent({
165
+ const lease = await collaboration.createClaim({
138
166
  target: {
139
167
  model: wireModel,
140
168
  id,
@@ -151,9 +179,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
151
179
  // Only when we actually waited behind another holder can the row have
152
180
  // changed underneath us — re-read so the claimed snapshot reflects what
153
181
  // they committed before releasing. Two signals, either suffices:
154
- // - `lease.waited` — the server granted via `intent_granted`, i.e. we
182
+ // - `lease.waited` — the server granted via `claim_granted`, i.e. we
155
183
  // provably queued behind a holder. Authoritative; works even when
156
- // the local snapshot is blind (intent fan-out is entity-scoped, so
184
+ // the local snapshot is blind (claim fan-out is entity-scoped, so
157
185
  // org-wide-subscribed clients never observe peers' claims).
158
186
  // - `contended` — the local snapshot saw a holder up front. Kept for
159
187
  // the no-queue paths where no grant frame exists.
@@ -178,7 +206,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
178
206
  const release = () => releaseClaim(id);
179
207
  return {
180
208
  object: 'claim',
181
- claimId: lease.id,
209
+ claimId: lease.claimId,
182
210
  readAt: snapshot.stamp,
183
211
  target,
184
212
  action: options?.action ?? 'editing',
@@ -269,7 +297,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
269
297
  if (!collaboration) {
270
298
  throw new AbloValidationError(`Model "${schemaKey}" cannot claim a row without collaboration wiring.`, { code: 'model_claim_not_configured' });
271
299
  }
272
- autoLease = await collaboration.createIntent({
300
+ autoLease = await collaboration.createClaim({
273
301
  target: {
274
302
  model: wireModel,
275
303
  id,
@@ -299,8 +327,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
299
327
  });
300
328
  const effective = {
301
329
  ...opts,
302
- ...(autoLease ? { intent: autoLease } : {}),
303
- ...(isClaimHandle(claim) ? { intent: { id: claim.claimId } } : {}),
330
+ ...(autoLease ? { claim: autoLease } : {}),
331
+ ...(isClaimHandle(claim) ? { claim: { id: claim.claimId } } : {}),
304
332
  };
305
333
  try {
306
334
  syncClient.add(model, effective);
@@ -336,7 +364,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
336
364
  wait: 'confirmed',
337
365
  readAt: claimed.snapshot.stamp,
338
366
  onStale: 'reject',
339
- intent: claimed.lease,
367
+ claimRef: { id: claimed.lease.claimId },
340
368
  ...opts,
341
369
  }
342
370
  : {
@@ -351,7 +379,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
351
379
  }
352
380
  : {}),
353
381
  ...opts,
354
- ...(handle ? { intent: { id: handle.claimId } } : {}),
382
+ ...(handle ? { claim: { id: handle.claimId } } : {}),
355
383
  };
356
384
  // Local user update: `applyChanges` keeps change tracking ON so
357
385
  // the edited fields land in `modifiedProperties` and actually get
@@ -386,7 +414,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
386
414
  wait: 'confirmed',
387
415
  readAt: claimed.snapshot.stamp,
388
416
  onStale: 'reject',
389
- intent: claimed.lease,
417
+ claimRef: { id: claimed.lease.claimId },
390
418
  ...opts,
391
419
  }
392
420
  : {
@@ -398,7 +426,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
398
426
  }
399
427
  : {}),
400
428
  ...opts,
401
- ...(handle ? { intent: { id: handle.claimId } } : {}),
429
+ ...(handle ? { claim: { id: handle.claimId } } : {}),
402
430
  };
403
431
  syncClient.delete(model, effective);
404
432
  await waitForMutation(model, effective);
@@ -55,6 +55,8 @@ export interface HttpModelClient<T, C = T> {
55
55
  export type AbloHttpClient<S extends SchemaRecord> = {
56
56
  readonly [K in keyof S & string]: HttpModelClient<InferModel<Schema<S>, K>, InferCreate<Schema<S>, K>>;
57
57
  } & {
58
+ /** Register `databaseUrl` when configured. Also runs lazily before the first request. */
59
+ ready(): Promise<void>;
58
60
  readonly commits: CommitResource;
59
61
  dispose(): Promise<void>;
60
62
  /** Resolve the bearer credential this client authenticates with (see `AbloApi.getAuthToken`). */
@@ -26,7 +26,7 @@ import { createProtocolClient, } from './ApiClient.js';
26
26
  /**
27
27
  * Members of the underlying `AbloApi` that pass straight through the facade.
28
28
  * Deliberately EXCLUDES the resource names that collide with common schema model
29
- * names — `tasks`, `intents`, `capabilities`, `agent` — so `client.tasks` resolves
29
+ * names — `tasks`, `claims`, `capabilities`, `agent` — so `client.tasks` resolves
30
30
  * to the schema model `tasks`, not the protocol `TaskResource`. Only lifecycle +
31
31
  * the genuinely-protocol methods an agent uses pass through.
32
32
  */
@@ -29,8 +29,8 @@
29
29
  * });
30
30
  * ```
31
31
  */
32
- export { Ablo, computeFKDepthPriority, type AbloOptions, type InternalAbloOptions, type ClaimedOptions, type IfClaimedPolicy, type IntentWaitOptions, type ModelCountOptions, type ModelListOptions, type ModelListScope, type ModelLoadOptions, type ModelOperations, type ModelReadOptions, } from './Ablo.js';
32
+ export { Ablo, computeFKDepthPriority, type AbloOptions, type InternalAbloOptions, type ClaimedOptions, type IfClaimedPolicy, type ClaimWaitOptions, type ModelCountOptions, type ModelListOptions, type ModelListScope, type ModelLoadOptions, type ModelOperations, type ModelReadOptions, } from './Ablo.js';
33
33
  export { ABLO_DEFAULT_BASE_URL, ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, normalizeAbloHostedBaseUrl, } from './auth.js';
34
34
  export type { AbloPersistence } from './persistence.js';
35
- export type { AbloApi, AbloApiClientOptions, AbloApiIntents, Capability, CapabilityCreateOptions, CapabilityParticipantKind, CapabilityRecord, CapabilityResource, CapabilityRevocation, CapabilityScope, } from './ApiClient.js';
36
- export type { EngineParticipant, JoinedParticipant, ParticipantJoinOptions, ParticipantManager, ParticipantScope, ParticipantStatus, ScopedIntents, ScopedPresence, } from '../sync/participants.js';
35
+ export type { AbloApi, AbloApiClientOptions, AbloApiClaims, Capability, CapabilityCreateOptions, CapabilityParticipantKind, CapabilityRecord, CapabilityResource, CapabilityRevocation, CapabilityScope, } from './ApiClient.js';
36
+ export type { EngineParticipant, JoinedParticipant, ParticipantJoinOptions, ParticipantManager, ParticipantScope, ParticipantStatus, ScopedClaims, ScopedPresence, } from '../sync/participants.js';
@@ -10,8 +10,8 @@
10
10
  * bottom so the two can never silently diverge.
11
11
  *
12
12
  * Validation-only by design: callers keep their ORIGINAL options object.
13
- * Zod's parse output strips unknown keys, and the `intent` slot legally
14
- * carries live handles (`IntentLeaseHandle` / claim leases) whose
13
+ * Zod's parse output strips unknown keys, and the `claim` slot legally
14
+ * carries live handles (`ClaimHandle` / claim leases) whose
15
15
  * `release`/`revoke` functions must survive — so we assert, never replace.
16
16
  */
17
17
  import { z } from 'zod';
@@ -25,8 +25,8 @@ export declare const writeOptionsSchema: z.ZodObject<{
25
25
  idempotencyKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
26
26
  label: z.ZodOptional<z.ZodString>;
27
27
  wait: z.ZodOptional<z.ZodEnum<{
28
- confirmed: "confirmed";
29
28
  queued: "queued";
29
+ confirmed: "confirmed";
30
30
  }>>;
31
31
  readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
32
32
  onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
@@ -35,7 +35,7 @@ export declare const writeOptionsSchema: z.ZodObject<{
35
35
  flag: "flag";
36
36
  merge: "merge";
37
37
  }>>>;
38
- intent: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
38
+ claim: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
39
39
  id: z.ZodString;
40
40
  }, z.core.$loose>]>>>;
41
41
  causedByTaskId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -10,8 +10,8 @@
10
10
  * bottom so the two can never silently diverge.
11
11
  *
12
12
  * Validation-only by design: callers keep their ORIGINAL options object.
13
- * Zod's parse output strips unknown keys, and the `intent` slot legally
14
- * carries live handles (`IntentLeaseHandle` / claim leases) whose
13
+ * Zod's parse output strips unknown keys, and the `claim` slot legally
14
+ * carries live handles (`ClaimHandle` / claim leases) whose
15
15
  * `release`/`revoke` functions must survive — so we assert, never replace.
16
16
  */
17
17
  import { z } from 'zod';
@@ -28,9 +28,9 @@ export const writeOptionsSchema = z.object({
28
28
  readAt: z.number().int().nonnegative().nullish(),
29
29
  /** What the server does when the target moved past `readAt`. */
30
30
  onStale: onStaleModeSchema.nullish(),
31
- /** Claim/intent attribution — an id, or a live lease handle (loose: the
31
+ /** Claim/claim attribution — an id, or a live lease handle (loose: the
32
32
  * handle's `release`/`revoke` functions ride along untouched). */
33
- intent: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
33
+ claim: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
34
34
  /** Dormant wire-compat field; always `null` from current clients. */
35
35
  causedByTaskId: z.string().nullish(),
36
36
  });