@abloatai/ablo 0.10.1 → 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 (93) hide show
  1. package/CHANGELOG.md +10 -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/package.json +3 -2
  90. package/dist/react/useIntent.js +0 -42
  91. package/dist/sync/awaitIntentGrant.d.ts +0 -40
  92. package/dist/sync/awaitIntentGrant.js +0 -62
  93. package/dist/sync/createIntentStream.d.ts +0 -34
@@ -20,15 +20,17 @@
20
20
  */
21
21
  import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
22
22
  import type { SyncEngineConfig, SyncLogger, MutationExecutor, MutationDispatcher, SyncObservabilityProvider, SyncAnalytics, SessionErrorDetector, OnlineStatusProvider } from '../interfaces/index.js';
23
+ import type { ModelTarget, ModelClaim } from '../coordination/schema.js';
24
+ export type { ModelTarget, ModelClaim };
23
25
  import { ObjectPool } from '../ObjectPool.js';
24
26
  import type { SyncStoreContract } from '../react/context.js';
25
27
  import type { SyncWebSocket } from '../sync/SyncWebSocket.js';
26
28
  import type { SyncGroupInput } from '../schema/roles.js';
27
29
  import { type SyncStatus } from '../BaseSyncedStore.js';
28
- import type { IntentStream, IntentWaitOptions, PresenceStream, Snapshot } from '../types/streams.js';
30
+ import type { ClaimStream, ClaimWaitOptions, PresenceStream, Snapshot } from '../types/streams.js';
29
31
  import type { ParticipantManager } from '../sync/participants.js';
30
- import type { ActiveIntent, Duration, Intent, TargetRange } from '../types/streams.js';
31
- import { type AbloApi, type AbloApiClientOptions, type AbloApiIntents } from './ApiClient.js';
32
+ import type { ClaimHandle, Duration, Claim } from '../types/streams.js';
33
+ import { type AbloApi, type AbloApiClientOptions, type AbloApiClaims } from './ApiClient.js';
32
34
  import { type AbloHttpClient, type AbloHttpClientOptions } from './httpClient.js';
33
35
  /**
34
36
  * Async function that resolves an apiKey at request time. Use for
@@ -384,30 +386,9 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
384
386
  * `claim({ id })` — durable claim handle for coordinated writes
385
387
  */
386
388
  export type { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './createModelProxy.js';
387
- import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelLoadOptions } from './createModelProxy.js';
389
+ import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ModelLoadOptions } from './createModelProxy.js';
388
390
  export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
389
391
  export type CommitWait = 'queued' | 'confirmed';
390
- export interface ModelTarget {
391
- /** The model name — matches `ablo.<model>` and the schema's `model()`. */
392
- readonly model: string;
393
- readonly id: string;
394
- readonly path?: string;
395
- readonly range?: TargetRange;
396
- readonly field?: string;
397
- readonly meta?: Record<string, unknown>;
398
- }
399
- export interface ModelClaim {
400
- readonly id: string;
401
- readonly actor: string;
402
- readonly participantKind: ActiveIntent['participantKind'];
403
- readonly action: string;
404
- readonly description?: string;
405
- readonly field?: string;
406
- readonly status?: 'active' | 'queued';
407
- readonly position?: number;
408
- readonly expiresAt: string;
409
- readonly target: ModelTarget;
410
- }
411
392
  export interface ModelRead<T = Record<string, unknown>> {
412
393
  readonly data: T;
413
394
  readonly stamp: number;
@@ -431,18 +412,18 @@ export interface ClaimedOptions {
431
412
  */
432
413
  readonly maxQueueDepth?: number;
433
414
  }
434
- export type { IntentWaitOptions } from '../types/streams.js';
415
+ export type { ClaimWaitOptions } from '../types/streams.js';
435
416
  export interface ModelReadOptions extends ClaimedOptions {
436
417
  }
437
- export interface IntentCreateOptions {
418
+ export interface ClaimCreateOptions {
438
419
  readonly target: ModelTarget;
439
420
  readonly action: string;
440
421
  readonly ttl?: Duration;
441
422
  /**
442
423
  * Join the server's fair FIFO queue when the target is already claimed,
443
424
  * rather than failing immediately. `create` then resolves only once the
444
- * lease is actually ours (the server pushes `intent_acquired` if the target
445
- * was free, or `intent_granted` when we reach the head of the line). Without
425
+ * lease is actually ours (the server pushes `claim_acquired` if the target
426
+ * was free, or `claim_granted` when we reach the head of the line). Without
446
427
  * this, a contended claim throws. Used by `ablo.<model>.claim` so writers
447
428
  * serialize instead of racing.
448
429
  */
@@ -455,20 +436,6 @@ export interface IntentCreateOptions {
455
436
  */
456
437
  readonly maxQueueDepth?: number;
457
438
  }
458
- export interface IntentHandle extends AsyncDisposable {
459
- readonly id: string;
460
- /**
461
- * True when the lease was granted AFTER waiting in the server's FIFO
462
- * queue behind a holder (`intent_granted`), false/absent for an
463
- * immediate grant (`intent_acquired`). Consumers re-read the target
464
- * row when this is set — the row may have changed while we queued, and
465
- * the local coordination snapshot can't detect that for org-scoped
466
- * subscriptions (intent fan-out is entity-scoped).
467
- */
468
- readonly waited?: boolean;
469
- release(): Promise<void>;
470
- revoke(): void;
471
- }
472
439
  export interface CommitOperationInput {
473
440
  readonly action: ModelOperationAction;
474
441
  /** The model name — matches `ablo.<model>` and the schema's `model()`. */
@@ -481,7 +448,7 @@ export interface CommitOperationInput {
481
448
  readonly onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
482
449
  }
483
450
  export interface CommitCreateOptions {
484
- readonly intent?: string | {
451
+ readonly claimRef?: string | {
485
452
  readonly id: string;
486
453
  } | null;
487
454
  readonly idempotencyKey?: string | null;
@@ -508,13 +475,13 @@ export interface CommitReceipt {
508
475
  export interface CommitResource {
509
476
  create(options: CommitCreateOptions): Promise<CommitReceipt>;
510
477
  }
511
- export interface IntentResource extends IntentStream {
512
- create(options: IntentCreateOptions): Promise<IntentHandle>;
478
+ export interface ClaimResource extends ClaimStream {
479
+ create(options: ClaimCreateOptions): Promise<ClaimHandle>;
513
480
  list(target?: Partial<ModelTarget>): readonly ModelClaim[];
514
- waitFor(target: Partial<ModelTarget>, options?: IntentWaitOptions): Promise<void>;
481
+ waitFor(target: Partial<ModelTarget>, options?: ClaimWaitOptions): Promise<void>;
515
482
  }
516
483
  export interface ModelMutationOptions extends ClaimedOptions {
517
- readonly intent?: string | {
484
+ readonly claimRef?: string | {
518
485
  readonly id: string;
519
486
  } | null;
520
487
  readonly idempotencyKey?: string | null;
@@ -537,14 +504,14 @@ export interface HttpClaimApi<T> {
537
504
  * Current holder of the lease on a row, or `null` when free. For UI badges,
538
505
  * preflight checks, and operators.
539
506
  */
540
- state(params: ClaimLookupParams<T>): Promise<Intent | null>;
507
+ state(params: ClaimLookupParams<T>): Promise<Claim | null>;
541
508
  /**
542
509
  * FIFO wait line behind the holder. Advanced: useful for operator UIs and
543
510
  * schedulers.
544
511
  */
545
512
  queue(params: ClaimLookupParams<T>): Promise<{
546
513
  readonly object: 'list';
547
- readonly data: readonly Intent[];
514
+ readonly data: readonly Claim[];
548
515
  }>;
549
516
  /**
550
517
  * Re-rank the wait line. Advanced and permission-gated.
@@ -859,7 +826,7 @@ export type Ablo<S extends SchemaRecord> = {
859
826
  * Canonical multiplayer participant surface. Joins a structured app
860
827
  * target, derives the transport scope internally, opens a scoped
861
828
  * claim on the existing WebSocket, and returns target-bound presence
862
- * + intent helpers.
829
+ * + claim helpers.
863
830
  *
864
831
  * ```ts
865
832
  * const participant = await ablo.participants.join({
@@ -869,7 +836,7 @@ export type Ablo<S extends SchemaRecord> = {
869
836
  * range: { startLine: 10, endLine: 40 },
870
837
  * });
871
838
  * participant.presence.editing();
872
- * const claim = participant.intents.claim('rewrite imports');
839
+ * const claim = participant.claims.claim('rewrite imports');
873
840
  * ```
874
841
  */
875
842
  readonly participants: ParticipantManager;
@@ -1000,7 +967,7 @@ import type * as _SchemaTypes from '../schema/schema.js';
1000
967
  export declare namespace Ablo {
1001
968
  type Options<S extends SchemaRecord = SchemaRecord> = AbloOptions<S>;
1002
969
  type Api = AbloApi;
1003
- type ApiIntents = AbloApiIntents;
970
+ type ApiClaims = AbloApiClaims;
1004
971
  type Capability = import('./ApiClient.js').Capability;
1005
972
  type CapabilityCreateOptions = import('./ApiClient.js').CapabilityCreateOptions;
1006
973
  type CapabilityRecord = import('./ApiClient.js').CapabilityRecord;
@@ -1015,13 +982,13 @@ export declare namespace Ablo {
1015
982
  type TargetRange = _Streams.TargetRange;
1016
983
  type Duration = _Streams.Duration;
1017
984
  type PresenceStream = _Streams.PresenceStream;
1018
- type IntentStream = _Streams.IntentStream;
985
+ type ClaimStream = _Streams.ClaimStream;
1019
986
  type Peer = _Streams.Peer;
1020
987
  type Activity = _Streams.Activity;
1021
- type ActiveIntent = _Streams.ActiveIntent;
1022
- type Claim = _Streams.Claim;
1023
- type IntentRejection = _Streams.IntentRejection;
1024
- type IntentLost = _Streams.IntentLost;
988
+ type ActiveClaim = _Streams.ActiveClaim;
989
+ type ClaimHandle = _Streams.ClaimHandle;
990
+ type ClaimRejection = _Streams.ClaimRejection;
991
+ type ClaimLost = _Streams.ClaimLost;
1025
992
  type Snapshot<TSchema extends _SchemaTypes.Schema = _SchemaTypes.Schema, K extends keyof TSchema['models'] = keyof TSchema['models']> = _Streams.Snapshot<TSchema, K>;
1026
993
  namespace Auth {
1027
994
  type Principal = _Streams.Principal;
@@ -1057,11 +1024,11 @@ export declare namespace Ablo {
1057
1024
  type Receipt = import('./Ablo.js').CommitReceipt;
1058
1025
  type Client = import('./Ablo.js').CommitResource;
1059
1026
  }
1060
- namespace Intent {
1061
- type Handle = import('./Ablo.js').IntentHandle;
1062
- type CreateOptions = import('./Ablo.js').IntentCreateOptions;
1063
- type WaitOptions = import('./Ablo.js').IntentWaitOptions;
1064
- type Client = import('./Ablo.js').IntentResource;
1027
+ namespace Claim {
1028
+ type Handle = import('./Ablo.js').ClaimHandle;
1029
+ type CreateOptions = import('./Ablo.js').ClaimCreateOptions;
1030
+ type WaitOptions = import('./Ablo.js').ClaimWaitOptions;
1031
+ type Client = import('./Ablo.js').ClaimResource;
1065
1032
  }
1066
1033
  namespace Model {
1067
1034
  type Target = import('./Ablo.js').ModelTarget;
@@ -19,7 +19,9 @@
19
19
  * await sync.reports.delete({ id: reportId });
20
20
  */
21
21
  import { z } from 'zod';
22
- import { AbloClaimedError, AbloError, AbloAuthenticationError, AbloConnectionError, AbloValidationError, translateHttpError, toAbloError } from '../errors.js';
22
+ import { baseFieldsSchema } from '../schema/schema.js';
23
+ import { AbloError, AbloAuthenticationError, AbloConnectionError, AbloValidationError, translateHttpError, toAbloError, claimedError } from '../errors.js';
24
+ import { descriptionFromMeta } from '../coordination/schema.js';
23
25
  import { LoadStrategy, PropertyType } from '../types/index.js';
24
26
  import { initSyncEngine } from '../context.js';
25
27
  import { noopObservability, browserOnlineStatus, defaultSessionErrorDetector, noopAnalytics, } from '../SyncEngineContext.js';
@@ -32,8 +34,8 @@ import { resolveParticipantIdentity } from './identity.js';
32
34
  import { Model } from '../Model.js';
33
35
  import { BaseSyncedStore } from '../BaseSyncedStore.js';
34
36
  import { createPresenceStream } from '../sync/createPresenceStream.js';
35
- import { createIntentStream } from '../sync/createIntentStream.js';
36
- import { awaitIntentGrant } from '../sync/awaitIntentGrant.js';
37
+ import { createClaimStream } from '../sync/createClaimStream.js';
38
+ import { awaitClaimGrant } from '../sync/awaitClaimGrant.js';
37
39
  import { createSnapshot } from '../sync/createSnapshot.js';
38
40
  import { createParticipantManager } from '../sync/participants.js';
39
41
  import { createProtocolClient, } from './ApiClient.js';
@@ -240,7 +242,18 @@ function registerModelsFromSchema(schema, registry) {
240
242
  }
241
243
  // Create a dynamic Model subclass with JSON sub-property getters
242
244
  const isLazy = modelDef.lazyObservable === true;
243
- const fieldNames = Object.keys(modelDef.shape);
245
+ // Base provenance fields (`organizationId`, `createdBy`) live in
246
+ // `baseFieldsSchema`, not the per-model `shape`. The server stamps + emits
247
+ // them (camelCased on the wire), but hydration (`Model.assignFieldsFromData`)
248
+ // only assigns keys that already exist as an own/prototype property — so
249
+ // without a slot here, `deck.createdBy` / `deck.organizationId` silently read
250
+ // `undefined` (this is why the profile decks tab showed nothing: it filters
251
+ // `decks.filter(d => d.createdBy === userId)`). `id`/`createdAt`/`updatedAt`
252
+ // are already seeded by the base Model constructor, so they're excluded.
253
+ const fieldNames = [
254
+ ...Object.keys(modelDef.shape),
255
+ ...Object.keys(baseFieldsSchema.shape).filter((f) => f !== 'id' && f !== 'createdAt' && f !== 'updatedAt' && !(f in modelDef.shape)),
256
+ ];
244
257
  const computed = modelDef.computed;
245
258
  const DynamicModel = createDynamicModelClass(modelName, jsonSubFields, fieldNames, computed, isLazy);
246
259
  // Respect the schema's load strategy so lazy models skip IDB hydration + bootstrap
@@ -821,8 +834,8 @@ export function Ablo(options) {
821
834
  // becomes null, so the first Ablo's commits start throwing
822
835
  // `ws_not_ready` forever (terminal AgentJob writes hang on retry).
823
836
  syncClient.getTransactionQueue().setMutationExecutor(executor);
824
- // Presence + intent streams — built eagerly so `engine.presence`
825
- // and `engine.intents` return the same reference for the engine's
837
+ // Presence + claim streams — built eagerly so `engine.presence`
838
+ // and `engine.claims` return the same reference for the engine's
826
839
  // lifetime. The transport doesn't exist yet (BaseSyncedStore.initialize
827
840
  // creates it during ready()), so both streams are constructed in
828
841
  // deferred-attach mode and wired after initialize() resolves below.
@@ -837,12 +850,12 @@ export function Ablo(options) {
837
850
  syncGroups: internalOptions.syncGroups ?? [],
838
851
  isAgent: internalOptions.kind === 'agent',
839
852
  });
840
- const intentStream = createIntentStream({ participantId });
853
+ const claimStream = createClaimStream({ participantId });
841
854
  const participantManager = createParticipantManager({
842
855
  ready,
843
856
  getTransport: () => store.getSyncWebSocket() ?? null,
844
857
  presence: presenceStream,
845
- intents: intentStream,
858
+ claims: claimStream,
846
859
  schema,
847
860
  });
848
861
  // 6. Validate options up front — fail loudly on obviously wrong inputs so
@@ -990,14 +1003,14 @@ export function Ablo(options) {
990
1003
  code: 'bootstrap_fetch_timeout',
991
1004
  });
992
1005
  }
993
- // Wire presence + intents to the now-open transport.
1006
+ // Wire presence + claims to the now-open transport.
994
1007
  // `getSyncWebSocket()` returns non-null after a successful
995
1008
  // initialize() — the WS is created during the generator's
996
1009
  // connect step.
997
1010
  const ws = store.getSyncWebSocket();
998
1011
  if (ws) {
999
1012
  presenceStream.attach(ws);
1000
- intentStream.attach(ws);
1013
+ claimStream.attach(ws);
1001
1014
  }
1002
1015
  logger.info('Sync engine ready', { models: Object.keys(schema.models).length });
1003
1016
  }
@@ -1073,10 +1086,10 @@ export function Ablo(options) {
1073
1086
  ? crypto.randomUUID()
1074
1087
  : `id_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1075
1088
  }
1076
- function normalizeIntentId(intent) {
1077
- if (typeof intent === 'string')
1078
- return intent;
1079
- return intent?.id;
1089
+ function normalizeClaimId(claim) {
1090
+ if (typeof claim === 'string')
1091
+ return claim;
1092
+ return claim?.id;
1080
1093
  }
1081
1094
  function isClaimHandleValue(value) {
1082
1095
  return (typeof value === 'object' &&
@@ -1113,82 +1126,72 @@ export function Ablo(options) {
1113
1126
  }
1114
1127
  return inputOperations.map((op) => normalizeCommitOperation(op, commitOptions));
1115
1128
  }
1116
- function modelClaimFromActive(intent) {
1117
- const description = typeof intent.target.meta?.description === 'string'
1118
- ? intent.target.meta.description
1119
- : undefined;
1129
+ function modelClaimFromActive(claim) {
1130
+ const description = descriptionFromMeta(claim.target.meta);
1120
1131
  return {
1121
- id: intent.id,
1122
- actor: intent.heldBy,
1123
- participantKind: intent.participantKind,
1124
- action: intent.reason,
1132
+ id: claim.id,
1133
+ actor: claim.heldBy,
1134
+ participantKind: claim.participantKind,
1135
+ action: claim.reason,
1125
1136
  ...(description ? { description } : {}),
1126
- field: intent.target.field,
1137
+ field: claim.target.field,
1127
1138
  status: 'active',
1128
- expiresAt: intent.expiresAt,
1139
+ expiresAt: claim.expiresAt,
1129
1140
  target: {
1130
- model: intent.target.type,
1131
- id: intent.target.id,
1132
- path: intent.target.path,
1133
- range: intent.target.range,
1134
- field: intent.target.field,
1135
- meta: intent.target.meta,
1141
+ model: claim.target.type,
1142
+ id: claim.target.id,
1143
+ path: claim.target.path,
1144
+ range: claim.target.range,
1145
+ field: claim.target.field,
1146
+ meta: claim.target.meta,
1136
1147
  },
1137
1148
  };
1138
1149
  }
1139
- function modelClaimFromQueued(intent) {
1150
+ function modelClaimFromQueued(claim) {
1140
1151
  return {
1141
- id: intent.id,
1142
- actor: intent.heldBy,
1143
- participantKind: intent.participantKind,
1144
- action: intent.action,
1145
- ...(intent.description ? { description: intent.description } : {}),
1146
- field: intent.target.field,
1152
+ id: claim.id,
1153
+ actor: claim.heldBy,
1154
+ participantKind: claim.participantKind,
1155
+ action: claim.action,
1156
+ ...(claim.description ? { description: claim.description } : {}),
1157
+ field: claim.target.field,
1147
1158
  status: 'queued',
1148
- position: intent.position,
1149
- expiresAt: intent.expiresAt,
1159
+ position: claim.position,
1160
+ expiresAt: claim.expiresAt,
1150
1161
  target: {
1151
- model: intent.target.type,
1152
- id: intent.target.id,
1153
- path: intent.target.path,
1154
- range: intent.target.range,
1155
- field: intent.target.field,
1156
- meta: intent.target.meta,
1162
+ model: claim.target.type,
1163
+ id: claim.target.id,
1164
+ path: claim.target.path,
1165
+ range: claim.target.range,
1166
+ field: claim.target.field,
1167
+ meta: claim.target.meta,
1157
1168
  },
1158
1169
  };
1159
1170
  }
1160
- function targetMatchesModel(target, intent) {
1171
+ function targetMatchesModel(target, claim) {
1161
1172
  if (target.model &&
1162
- intent.target.type.toLowerCase() !== target.model.toLowerCase()) {
1173
+ claim.target.type.toLowerCase() !== target.model.toLowerCase()) {
1163
1174
  return false;
1164
1175
  }
1165
- if (target.id && intent.target.id !== target.id)
1176
+ if (target.id && claim.target.id !== target.id)
1166
1177
  return false;
1167
- if (target.field && intent.target.field !== target.field)
1178
+ if (target.field && claim.target.field !== target.field)
1168
1179
  return false;
1169
1180
  return true;
1170
1181
  }
1171
1182
  function listModelClaims(target) {
1172
- return intentStream.others
1173
- .filter((intent) => (target ? targetMatchesModel(target, intent) : true))
1183
+ return claimStream.others
1184
+ .filter((claim) => (target ? targetMatchesModel(target, claim) : true))
1174
1185
  .map(modelClaimFromActive);
1175
1186
  }
1176
1187
  function listModelClaimQueue(target) {
1177
1188
  if (!target?.model || !target.id)
1178
1189
  return [];
1179
- return publicIntents
1190
+ return publicClaims
1180
1191
  .queueFor({ type: target.model, id: target.id })
1181
- .filter((intent) => (target.field ? intent.target.field === target.field : true))
1192
+ .filter((claim) => (target.field ? claim.target.field === target.field : true))
1182
1193
  .map(modelClaimFromQueued);
1183
1194
  }
1184
- function claimedError(target, claims, code) {
1185
- const label = [target.model, target.id, target.field].filter(Boolean).join('/');
1186
- const holder = claims[0];
1187
- const suffix = holder
1188
- ? ` held by ${holder.actor} (${holder.action})`
1189
- : ' held by another participant';
1190
- return new AbloClaimedError(`Model row is claimed: ${label || 'target'}${suffix}.`, { code, claims });
1191
- }
1192
1195
  function waitForModelUnclaimed(target, options) {
1193
1196
  if (listModelClaims(target).length === 0)
1194
1197
  return Promise.resolve();
@@ -1216,8 +1219,8 @@ export function Ablo(options) {
1216
1219
  }
1217
1220
  };
1218
1221
  const onAbort = () => {
1219
- finish(() => reject(new AbloConnectionError('Intent wait aborted.', {
1220
- code: 'intent_wait_aborted',
1222
+ finish(() => reject(new AbloConnectionError('Claim wait aborted.', {
1223
+ code: 'claim_wait_aborted',
1221
1224
  cause: options?.signal?.reason,
1222
1225
  })));
1223
1226
  };
@@ -1225,7 +1228,7 @@ export function Ablo(options) {
1225
1228
  onAbort();
1226
1229
  return;
1227
1230
  }
1228
- unsubscribe = intentStream.onChange(check);
1231
+ unsubscribe = claimStream.onChange(check);
1229
1232
  options?.signal?.addEventListener('abort', onAbort, { once: true });
1230
1233
  if (options?.timeout != null) {
1231
1234
  timeoutId = setTimeout(() => {
@@ -1250,58 +1253,61 @@ export function Ablo(options) {
1250
1253
  }
1251
1254
  await waitForModelUnclaimed(target, { timeout: options?.claimedTimeout });
1252
1255
  }
1253
- function wrapIntentHandle(claim, waited = false) {
1256
+ function wrapClaimHandle(claim, waited = false) {
1254
1257
  const release = async () => {
1255
1258
  claim.revoke();
1256
1259
  };
1257
1260
  return {
1258
- id: claim.id,
1261
+ object: 'claim',
1262
+ claimId: claim.claimId,
1263
+ action: claim.action,
1264
+ target: claim.target,
1259
1265
  waited,
1260
1266
  release,
1261
1267
  revoke: claim.revoke,
1262
1268
  [Symbol.asyncDispose]: release,
1263
1269
  };
1264
1270
  }
1265
- const publicIntents = Object.assign(intentStream, {
1266
- async create(intentOptions) {
1271
+ const publicClaims = Object.assign(claimStream, {
1272
+ async create(claimOptions) {
1267
1273
  await ready();
1268
- const claim = intentStream.claim({
1269
- type: intentOptions.target.model,
1270
- id: intentOptions.target.id,
1271
- path: intentOptions.target.path,
1272
- range: intentOptions.target.range,
1273
- field: intentOptions.target.field,
1274
- meta: intentOptions.target.meta,
1274
+ const claim = claimStream.claim({
1275
+ type: claimOptions.target.model,
1276
+ id: claimOptions.target.id,
1277
+ path: claimOptions.target.path,
1278
+ range: claimOptions.target.range,
1279
+ field: claimOptions.target.field,
1280
+ meta: claimOptions.target.meta,
1275
1281
  }, {
1276
- reason: intentOptions.action,
1277
- ttl: intentOptions.ttl,
1278
- queue: intentOptions.queue,
1282
+ reason: claimOptions.action,
1283
+ ttl: claimOptions.ttl,
1284
+ queue: claimOptions.queue,
1279
1285
  });
1280
1286
  // With `queue`, the claim is only really *ours* once the server says
1281
- // so (`intent_acquired` if the target was free, `intent_granted` once
1287
+ // so (`claim_acquired` if the target was free, `claim_granted` once
1282
1288
  // we reach the head of the FIFO line). Block here on that grant so
1283
1289
  // callers — chiefly `ablo.<model>.claim` — get a handle that already
1284
1290
  // holds the lease, never a half-claimed one racing the queue.
1285
1291
  let waited = false;
1286
- if (intentOptions.queue) {
1292
+ if (claimOptions.queue) {
1287
1293
  const ws = store.getSyncWebSocket();
1288
1294
  if (ws) {
1289
1295
  try {
1290
- ({ waited } = await awaitIntentGrant(ws, claim.id, {
1291
- timeoutMs: intentOptions.waitTimeoutMs,
1292
- maxQueueDepth: intentOptions.maxQueueDepth,
1296
+ ({ waited } = await awaitClaimGrant(ws, claim.claimId, {
1297
+ timeoutMs: claimOptions.waitTimeoutMs,
1298
+ maxQueueDepth: claimOptions.maxQueueDepth,
1293
1299
  }));
1294
1300
  }
1295
1301
  catch (err) {
1296
1302
  // Gave up waiting (queue too deep, timed out, or lost) — abandon
1297
- // the queued intent so we don't leave a phantom entry in the
1303
+ // the queued claim so we don't leave a phantom entry in the
1298
1304
  // line that would block or mislead other claimers.
1299
1305
  claim.revoke();
1300
1306
  throw err;
1301
1307
  }
1302
1308
  }
1303
1309
  }
1304
- return wrapIntentHandle(claim, waited);
1310
+ return wrapClaimHandle(claim, waited);
1305
1311
  },
1306
1312
  list(target) {
1307
1313
  return listModelClaims(target);
@@ -1310,14 +1316,14 @@ export function Ablo(options) {
1310
1316
  return waitForModelUnclaimed(target, options);
1311
1317
  },
1312
1318
  });
1313
- // Build the typed proxy — one property per model. Done after publicIntents
1319
+ // Build the typed proxy — one property per model. Done after publicClaims
1314
1320
  // exists so model clients can expose workflow helpers such as
1315
1321
  // `ablo.files.edit(...)` without importing protocol wiring.
1316
1322
  const modelProxies = {};
1317
1323
  for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
1318
1324
  const registeredModelName = modelDef.typename ?? schemaKey;
1319
1325
  modelProxies[schemaKey] = createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
1320
- createIntent: (intentOptions) => publicIntents.create(intentOptions),
1326
+ createClaim: (claimOptions) => publicClaims.create(claimOptions),
1321
1327
  createSnapshot: (modelKey, id) => createSnapshot({
1322
1328
  pool: objectPool,
1323
1329
  transport: store.getSyncWebSocket(),
@@ -1331,21 +1337,21 @@ export function Ablo(options) {
1331
1337
  getLastSyncId: () => syncClient.position.readFloor,
1332
1338
  entities: { [modelKey]: id },
1333
1339
  }),
1334
- queue: (target) => publicIntents.queueFor({ type: target.model, id: target.id }),
1335
- reorder: (target, order) => publicIntents.reorder({ type: target.model, id: target.id }, order),
1340
+ queue: (target) => publicClaims.queueFor({ type: target.model, id: target.id }),
1341
+ reorder: (target, order) => publicClaims.reorder({ type: target.model, id: target.id }, order),
1336
1342
  observe: (target) => {
1337
- // The live intent stream only tracks *open* (active) claims;
1343
+ // The live claim stream only tracks *open* (active) claims;
1338
1344
  // terminal states (committed / expired / canceled) drop out of
1339
1345
  // the list entirely — exactly the ephemeral coordination model.
1340
1346
  // So a present entry is, by definition, `status: 'active'`.
1341
- const held = publicIntents.list({
1347
+ const held = publicClaims.list({
1342
1348
  model: target.model,
1343
1349
  id: target.id,
1344
1350
  })[0];
1345
1351
  if (!held)
1346
1352
  return null;
1347
1353
  return {
1348
- object: 'intent',
1354
+ object: 'claim',
1349
1355
  id: held.id,
1350
1356
  status: 'active',
1351
1357
  target: {
@@ -1362,7 +1368,7 @@ export function Ablo(options) {
1362
1368
  expiresAt: held.expiresAt,
1363
1369
  };
1364
1370
  },
1365
- waitFor: (target, waitOptions) => publicIntents.waitFor({ model: target.model, id: target.id }, waitOptions),
1371
+ waitFor: (target, waitOptions) => publicClaims.waitFor({ model: target.model, id: target.id }, waitOptions),
1366
1372
  selfParticipantId: participantId,
1367
1373
  });
1368
1374
  }
@@ -1375,7 +1381,7 @@ export function Ablo(options) {
1375
1381
  readAt: commitOptions.readAt,
1376
1382
  onStale: commitOptions.onStale,
1377
1383
  wait: commitOptions.wait,
1378
- intent: commitOptions.intent,
1384
+ claim: commitOptions.claim,
1379
1385
  }, 'commits.create');
1380
1386
  const clientTxId = createClientTxId(commitOptions.idempotencyKey);
1381
1387
  // A claim handle supplies the batch stale-guard defaults — same
@@ -1388,8 +1394,8 @@ export function Ablo(options) {
1388
1394
  onStale: commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
1389
1395
  });
1390
1396
  const wait = commitOptions.wait ?? 'confirmed';
1391
- const intentId = normalizeIntentId(commitOptions.intent) ?? claim?.claimId;
1392
- void intentId; // The current wire clears intents by entity after commit.
1397
+ const claimId = normalizeClaimId(commitOptions.claimRef) ?? claim?.claimId;
1398
+ void claimId; // The current wire clears claims by entity after commit.
1393
1399
  // Route through the TransactionQueue's commit lane so the call
1394
1400
  // tolerates WS disconnects: the envelope stays in memory until
1395
1401
  // reconnect, mutationExecutor.commit() owns transport-level
@@ -1462,7 +1468,7 @@ export function Ablo(options) {
1462
1468
  const id = params.id ?? createModelId();
1463
1469
  await applyClaimedPolicy({ model: name, id }, params);
1464
1470
  return commits.create({
1465
- intent: params.intent,
1471
+ claimRef: params.claimRef,
1466
1472
  idempotencyKey: params.idempotencyKey,
1467
1473
  readAt: params.readAt,
1468
1474
  onStale: params.onStale,
@@ -1481,7 +1487,7 @@ export function Ablo(options) {
1481
1487
  async update(params) {
1482
1488
  await applyClaimedPolicy({ model: name, id: params.id }, params);
1483
1489
  return commits.create({
1484
- intent: params.intent,
1490
+ claimRef: params.claimRef,
1485
1491
  idempotencyKey: params.idempotencyKey,
1486
1492
  readAt: params.readAt,
1487
1493
  onStale: params.onStale,
@@ -1500,7 +1506,7 @@ export function Ablo(options) {
1500
1506
  async delete(params) {
1501
1507
  await applyClaimedPolicy({ model: name, id: params.id }, params);
1502
1508
  return commits.create({
1503
- intent: params.intent,
1509
+ claimRef: params.claimRef,
1504
1510
  idempotencyKey: params.idempotencyKey,
1505
1511
  readAt: params.readAt,
1506
1512
  onStale: params.onStale,
@@ -1659,7 +1665,7 @@ export function Ablo(options) {
1659
1665
  logger.warn('Error during sync engine disposal', { error: err.message });
1660
1666
  }
1661
1667
  presenceStream.dispose();
1662
- intentStream.dispose();
1668
+ claimStream.dispose();
1663
1669
  syncClient.dispose();
1664
1670
  },
1665
1671
  /**
@@ -1711,8 +1717,8 @@ export function Ablo(options) {
1711
1717
  /** Presence livestream — same socket as entity sync, no second
1712
1718
  * connection. Stable reference across the engine's lifetime. */
1713
1719
  presence: presenceStream,
1714
- /** Intent livestream — same socket. Stable reference. */
1715
- intents: publicIntents,
1720
+ /** Claim livestream — same socket. Stable reference. */
1721
+ claims: publicClaims,
1716
1722
  commits,
1717
1723
  model,
1718
1724
  /** Structured multiplayer participation — target-first, no