@abloatai/ablo 0.28.0 → 0.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/Database.js +9 -6
  3. package/dist/SyncClient.d.ts +2 -2
  4. package/dist/SyncClient.js +22 -10
  5. package/dist/adapters/inMemoryStorage.js +18 -14
  6. package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
  7. package/dist/auth/schemas.d.ts +6 -0
  8. package/dist/auth/schemas.js +7 -0
  9. package/dist/cli.cjs +815 -400
  10. package/dist/client/Ablo.d.ts +29 -23
  11. package/dist/client/Ablo.js +8 -196
  12. package/dist/client/claimHeartbeatLoop.d.ts +1 -1
  13. package/dist/client/claimHeartbeatLoop.js +1 -1
  14. package/dist/client/createInternalComponents.d.ts +4 -2
  15. package/dist/client/createInternalComponents.js +3 -1
  16. package/dist/client/durableWrites.d.ts +21 -0
  17. package/dist/client/durableWrites.js +46 -0
  18. package/dist/client/httpClient.d.ts +19 -45
  19. package/dist/client/httpClient.js +104 -26
  20. package/dist/client/httpTransport.d.ts +8 -0
  21. package/dist/client/{ApiClient.js → httpTransport.js} +78 -291
  22. package/dist/client/options.d.ts +43 -19
  23. package/dist/client/options.js +3 -2
  24. package/dist/client/resourceTypes.d.ts +9 -85
  25. package/dist/client/resourceTypes.js +1 -1
  26. package/dist/client/sessionMint.d.ts +5 -6
  27. package/dist/client/sessionMint.js +4 -5
  28. package/dist/client/validateAbloOptions.js +3 -3
  29. package/dist/coordination/schema.d.ts +30 -0
  30. package/dist/coordination/schema.js +14 -0
  31. package/dist/core/index.d.ts +1 -1
  32. package/dist/errors.d.ts +4 -40
  33. package/dist/errors.js +5 -5
  34. package/dist/index.d.ts +14 -8
  35. package/dist/index.js +7 -5
  36. package/dist/interfaces/index.d.ts +3 -0
  37. package/dist/mutators/defineMutators.d.ts +18 -0
  38. package/dist/mutators/defineMutators.js +4 -8
  39. package/dist/react/index.d.ts +1 -1
  40. package/dist/schema/index.d.ts +1 -1
  41. package/dist/schema/index.js +1 -1
  42. package/dist/schema/select.d.ts +15 -0
  43. package/dist/schema/select.js +27 -3
  44. package/dist/source/connector.d.ts +3 -3
  45. package/dist/source/factory.d.ts +4 -6
  46. package/dist/source/factory.js +4 -7
  47. package/dist/source/index.d.ts +4 -4
  48. package/dist/source/index.js +2 -2
  49. package/dist/source/signing.d.ts +0 -3
  50. package/dist/source/types.d.ts +0 -26
  51. package/dist/stores/ObjectStore.js +8 -8
  52. package/dist/surface.d.ts +1 -1
  53. package/dist/surface.js +1 -0
  54. package/dist/sync/BootstrapFetcher.d.ts +10 -0
  55. package/dist/sync/BootstrapFetcher.js +27 -4
  56. package/dist/sync/createClaimStream.d.ts +11 -2
  57. package/dist/sync/createClaimStream.js +4 -3
  58. package/dist/testing/mocks/FakeDatabase.d.ts +1 -1
  59. package/dist/transactions/TransactionQueue.d.ts +2 -2
  60. package/dist/transactions/TransactionQueue.js +3 -3
  61. package/dist/transactions/commitEnvelope.d.ts +1 -1
  62. package/dist/transactions/commitEnvelope.js +2 -2
  63. package/dist/transactions/commitOutboxStore.d.ts +19 -19
  64. package/dist/transactions/durableWriteStore.d.ts +123 -0
  65. package/dist/transactions/durableWriteStore.js +30 -0
  66. package/dist/transactions/httpCommitEnvelope.d.ts +1 -0
  67. package/dist/transactions/httpCommitEnvelope.js +14 -12
  68. package/dist/transactions/idempotencyKey.d.ts +10 -0
  69. package/dist/transactions/idempotencyKey.js +9 -0
  70. package/dist/types/global.d.ts +10 -29
  71. package/dist/types/global.js +4 -7
  72. package/dist/types/streams.d.ts +2 -28
  73. package/dist/wire/frames.d.ts +6 -257
  74. package/dist/wire/frames.js +4 -25
  75. package/dist/wire/index.d.ts +4 -3
  76. package/dist/wire/index.js +2 -2
  77. package/dist/wire/protocolVersion.d.ts +30 -17
  78. package/dist/wire/protocolVersion.js +34 -18
  79. package/docs/agents.md +8 -3
  80. package/docs/api.md +6 -4
  81. package/docs/client-behavior.md +6 -3
  82. package/docs/coordination.md +2 -3
  83. package/docs/data-sources.md +5 -8
  84. package/docs/guarantees.md +21 -0
  85. package/docs/integration-guide.md +1 -0
  86. package/docs/migration.md +21 -1
  87. package/docs/quickstart.md +11 -0
  88. package/docs/react.md +0 -46
  89. package/examples/README.md +6 -5
  90. package/llms.txt +15 -15
  91. package/package.json +2 -7
  92. package/dist/client/ApiClient.d.ts +0 -177
  93. package/dist/commit/contract.d.ts +0 -493
  94. package/dist/commit/contract.js +0 -187
  95. package/dist/commit/index.d.ts +0 -6
  96. package/dist/commit/index.js +0 -5
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Product-facing persistence contract for writes that must survive a process
3
+ * restart or an ambiguous network response.
4
+ *
5
+ * The engine implements this contract with a transactional outbox internally,
6
+ * but callers should only need to think in terms of pending durable writes.
7
+ */
8
+ import { z } from 'zod';
9
+ /** Every write shape that Ablo may ask an injected store to persist. */
10
+ export declare const pendingWriteSchema: z.ZodUnion<readonly [z.ZodObject<{
11
+ id: z.ZodString;
12
+ type: z.ZodLiteral<"commit_envelope">;
13
+ storageVersion: z.ZodLiteral<1>;
14
+ origin: z.ZodEnum<{
15
+ model_batch: "model_batch";
16
+ atomic_commit: "atomic_commit";
17
+ }>;
18
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
19
+ operations: z.ZodArray<z.ZodObject<{
20
+ type: z.ZodEnum<{
21
+ CREATE: "CREATE";
22
+ UPDATE: "UPDATE";
23
+ DELETE: "DELETE";
24
+ ARCHIVE: "ARCHIVE";
25
+ UNARCHIVE: "UNARCHIVE";
26
+ }>;
27
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
28
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
29
+ reject: "reject";
30
+ overwrite: "overwrite";
31
+ notify: "notify";
32
+ }>>>;
33
+ model: z.ZodString;
34
+ id: z.ZodString;
35
+ input: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
36
+ transactionId: z.ZodOptional<z.ZodString>;
37
+ options: z.ZodOptional<z.ZodObject<{
38
+ idempotencyKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
+ label: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strict>>;
41
+ }, z.core.$strip>>;
42
+ sourceMutationIds: z.ZodDefault<z.ZodArray<z.ZodString>>;
43
+ commitOptions: z.ZodDefault<z.ZodObject<{
44
+ causedByTaskId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
45
+ reads: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
46
+ model: z.ZodString;
47
+ id: z.ZodString;
48
+ readAt: z.ZodNumber;
49
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
+ onStale: z.ZodOptional<z.ZodEnum<{
51
+ reject: "reject";
52
+ overwrite: "overwrite";
53
+ notify: "notify";
54
+ }>>;
55
+ }, z.core.$strip>, z.ZodObject<{
56
+ group: z.ZodString;
57
+ readAt: z.ZodNumber;
58
+ onStale: z.ZodOptional<z.ZodEnum<{
59
+ reject: "reject";
60
+ overwrite: "overwrite";
61
+ notify: "notify";
62
+ }>>;
63
+ }, z.core.$strip>]>>>>;
64
+ }, z.core.$strict>>;
65
+ scope: z.ZodOptional<z.ZodObject<{
66
+ organizationId: z.ZodString;
67
+ participantId: z.ZodString;
68
+ namespace: z.ZodString;
69
+ }, z.core.$strict>>;
70
+ createdAt: z.ZodNumber;
71
+ sealedAt: z.ZodNumber;
72
+ sequence: z.ZodOptional<z.ZodNumber>;
73
+ timestamp: z.ZodNumber;
74
+ }, z.core.$strict>, z.ZodObject<{
75
+ id: z.ZodString;
76
+ type: z.ZodLiteral<"http_commit_envelope">;
77
+ storageVersion: z.ZodLiteral<1>;
78
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
79
+ protocolVersion: z.ZodDefault<z.ZodNumber>;
80
+ request: z.ZodObject<{
81
+ method: z.ZodEnum<{
82
+ DELETE: "DELETE";
83
+ POST: "POST";
84
+ PATCH: "PATCH";
85
+ }>;
86
+ path: z.ZodString;
87
+ body: z.ZodString;
88
+ }, z.core.$strict>;
89
+ scopeNamespace: z.ZodString;
90
+ createdAt: z.ZodNumber;
91
+ sealedAt: z.ZodNumber;
92
+ sequence: z.ZodOptional<z.ZodNumber>;
93
+ timestamp: z.ZodNumber;
94
+ }, z.core.$strict>]>;
95
+ export type PendingWrite = z.infer<typeof pendingWriteSchema>;
96
+ /**
97
+ * Persistence port used by `Ablo({ durableWrites })`.
98
+ *
99
+ * `seal` is the durability boundary: it must atomically persist the exact write
100
+ * and consume the staged records that write supersedes. Resolving this promise
101
+ * authorizes Ablo to dispatch the request, so adapters must never report success
102
+ * before the data is durable.
103
+ */
104
+ export interface DurableWriteStore {
105
+ /**
106
+ * Atomically reserve a pending write and consume the staged records it owns.
107
+ * The same id + same request is idempotent; the same id + a different request
108
+ * must be rejected.
109
+ */
110
+ seal(write: PendingWrite, consumedRecordIds: readonly string[]): Promise<void>;
111
+ /** Load all unacknowledged writes. Stored data is treated as untrusted. */
112
+ list(): Promise<readonly unknown[]>;
113
+ /** Remove one write only after its outcome is definitive. */
114
+ remove(writeId: string): Promise<void>;
115
+ }
116
+ /** Runtime validation for injected adapters, including JavaScript consumers. */
117
+ export declare const durableWriteStoreSchema: z.ZodCustom<DurableWriteStore, DurableWriteStore>;
118
+ /** Options for crash-durable `create`, `update`, and `delete` calls. */
119
+ export declare const durableWritesConfigSchema: z.ZodObject<{
120
+ store: z.ZodCustom<DurableWriteStore, DurableWriteStore>;
121
+ namespace: z.ZodOptional<z.ZodString>;
122
+ }, z.core.$strict>;
123
+ export type DurableWritesConfig = z.infer<typeof durableWritesConfigSchema>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Product-facing persistence contract for writes that must survive a process
3
+ * restart or an ambiguous network response.
4
+ *
5
+ * The engine implements this contract with a transactional outbox internally,
6
+ * but callers should only need to think in terms of pending durable writes.
7
+ */
8
+ import { z } from 'zod';
9
+ import { durableCommitEnvelopeSchema } from './commitEnvelope.js';
10
+ import { durableHttpCommitEnvelopeSchema } from './httpCommitEnvelope.js';
11
+ /** Every write shape that Ablo may ask an injected store to persist. */
12
+ export const pendingWriteSchema = z.union([
13
+ durableCommitEnvelopeSchema,
14
+ durableHttpCommitEnvelopeSchema,
15
+ ]);
16
+ /** Runtime validation for injected adapters, including JavaScript consumers. */
17
+ export const durableWriteStoreSchema = z.custom((value) => {
18
+ if (typeof value !== 'object' || value === null)
19
+ return false;
20
+ const candidate = value;
21
+ return (typeof candidate.seal === 'function' &&
22
+ typeof candidate.list === 'function' &&
23
+ typeof candidate.remove === 'function');
24
+ }, { message: 'store must implement seal(), list(), and remove()' });
25
+ /** Options for crash-durable `create`, `update`, and `delete` calls. */
26
+ export const durableWritesConfigSchema = z.strictObject({
27
+ store: durableWriteStoreSchema,
28
+ /** Separates deployments or workflow lanes that share an authenticated actor. */
29
+ namespace: z.string().trim().min(1).optional(),
30
+ });
@@ -11,6 +11,7 @@ export declare const durableHttpCommitEnvelopeSchema: z.ZodObject<{
11
11
  type: z.ZodLiteral<"http_commit_envelope">;
12
12
  storageVersion: z.ZodLiteral<1>;
13
13
  idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
14
+ protocolVersion: z.ZodDefault<z.ZodNumber>;
14
15
  request: z.ZodObject<{
15
16
  method: z.ZodEnum<{
16
17
  DELETE: "DELETE";
@@ -1,8 +1,9 @@
1
1
  /** Crash-durable exact HTTP request used by the stateless agent client. */
2
2
  import { z } from 'zod';
3
3
  import { v5 as uuidv5 } from 'uuid';
4
- import { idempotencyKeySchema } from '../commit/contract.js';
5
4
  import { stableStringify } from '../utils/json.js';
5
+ import { PROTOCOL_VERSION } from '../wire/protocolVersion.js';
6
+ import { idempotencyKeySchema } from './idempotencyKey.js';
6
7
  export const HTTP_COMMIT_ENVELOPE_VERSION = 1;
7
8
  export const HTTP_COMMIT_ENVELOPE_PREFIX = 'http-commit-envelope:';
8
9
  /** Stay one hour inside the server's 24-hour idempotency retention window. */
@@ -45,6 +46,11 @@ export const durableHttpCommitEnvelopeSchema = z
45
46
  type: z.literal('http_commit_envelope'),
46
47
  storageVersion: z.literal(HTTP_COMMIT_ENVELOPE_VERSION),
47
48
  idempotencyKey: idempotencyKeySchema,
49
+ /**
50
+ * The wire contract used when this exact request was sealed. Entries from
51
+ * before protocol versioning omitted the field and are v1 by definition.
52
+ */
53
+ protocolVersion: z.number().int().positive().default(1),
48
54
  request: z.strictObject({
49
55
  method: z.enum(['POST', 'PATCH', 'DELETE']),
50
56
  path: z.string().startsWith('/'),
@@ -111,21 +117,16 @@ export const durableHttpCommitEnvelopeSchema = z
111
117
  return;
112
118
  }
113
119
  const record = body;
114
- if (record.idempotencyKey !== envelope.idempotencyKey) {
115
- context.addIssue({
116
- code: 'custom',
117
- path: ['request', 'body', 'idempotencyKey'],
118
- message: 'HTTP body idempotency key must match its envelope',
119
- });
120
- }
120
+ // `envelope.idempotencyKey` is replayed as the `Idempotency-Key` header.
121
+ // Older sealed bodies may also carry `idempotencyKey`/`clientTxId`; accept
122
+ // and replay them byte-for-byte, but current requests do not duplicate the
123
+ // key in JSON.
121
124
  if (path === '/v1/commits') {
122
- if (record.clientTxId !== envelope.idempotencyKey ||
123
- !Array.isArray(record.operations) ||
124
- record.operations.length === 0) {
125
+ if (!Array.isArray(record.operations) || record.operations.length === 0) {
125
126
  context.addIssue({
126
127
  code: 'custom',
127
128
  path: ['request', 'body'],
128
- message: 'Commit-route body must carry the same clientTxId and operations',
129
+ message: 'Commit-route body must carry operations',
129
130
  });
130
131
  }
131
132
  }
@@ -162,6 +163,7 @@ export function createDurableHttpCommitEnvelope(input) {
162
163
  type: 'http_commit_envelope',
163
164
  storageVersion: HTTP_COMMIT_ENVELOPE_VERSION,
164
165
  idempotencyKey: input.idempotencyKey,
166
+ protocolVersion: PROTOCOL_VERSION,
165
167
  request: {
166
168
  method: input.request.method,
167
169
  path: input.request.path,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Identity shared by the durable write records.
3
+ *
4
+ * A retry must reuse the same non-empty key so the server can recognize the
5
+ * same logical write. This is a persistence invariant, not a second commit
6
+ * protocol.
7
+ */
8
+ import { z } from 'zod';
9
+ export declare const idempotencyKeySchema: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
10
+ export type IdempotencyKey = z.output<typeof idempotencyKeySchema>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Identity shared by the durable write records.
3
+ *
4
+ * A retry must reuse the same non-empty key so the server can recognize the
5
+ * same logical write. This is a persistence invariant, not a second commit
6
+ * protocol.
7
+ */
8
+ import { z } from 'zod';
9
+ export const idempotencyKeySchema = z.string().min(1).max(255).brand();
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * The single place where you tell the SDK about your application's types.
3
3
  *
4
- * You register your Schema, Presence, Claims, and UserMeta once by augmenting
5
- * the {@link Register} interface. From then on every SDK hook `useAblo`,
6
- * `useQuery`, `useOne`, `usePresence`, `useClaim` reads its types from that
7
- * registration, so you never pass a generic or a `schema` argument at a call
8
- * site.
4
+ * You register your Schema and UserMeta once by augmenting the
5
+ * {@link Register} interface. From then on the typed SDK surface reads its
6
+ * types from that registration, so you never pass a generic or a `schema`
7
+ * argument at a call site.
9
8
  *
10
9
  * Registration uses TypeScript module augmentation: any file in your project
11
10
  * can add an `interface Register` to a `declare module '@abloatai/ablo'`
@@ -27,8 +26,6 @@
27
26
  * declare module '@abloatai/ablo' {
28
27
  * interface Register {
29
28
  * Schema: typeof schema;
30
- * Presence: { cursor: { x: number; y: number } | null };
31
- * Claims: { editLayer: { layerId: string } };
32
29
  * UserMeta: { id: string; email: string };
33
30
  * }
34
31
  * }
@@ -39,18 +36,18 @@
39
36
  * {@link DefaultSyncShape} — a loose shape that keeps your code compiling
40
37
  * without typed results until you opt in.
41
38
  */
39
+ import type { SchemaRecord } from '../schema/schema.js';
42
40
  /**
43
41
  * The fallback shapes the resolvers use when {@link Register} has not been
44
42
  * augmented. `DefaultSyncShape.Schema` is deliberately structural — it carries
45
- * `{ models: Record<string, unknown> }` so hooks can still check a model-key
46
- * argument against something, just without a typed entity shape behind it.
43
+ * `{ models: SchemaRecord }` so hooks can still check a model-key argument
44
+ * against something (and satisfy the `SchemaRecord` bound), just without a
45
+ * typed entity shape behind it.
47
46
  */
48
47
  export interface DefaultSyncShape {
49
48
  readonly Schema: {
50
- readonly models: Record<string, unknown>;
49
+ readonly models: SchemaRecord;
51
50
  };
52
- readonly Presence: Record<string, unknown>;
53
- readonly Claims: Record<string, unknown>;
54
51
  readonly UserMeta: {
55
52
  readonly id: string;
56
53
  };
@@ -58,7 +55,7 @@ export interface DefaultSyncShape {
58
55
  /**
59
56
  * The registration interface you augment to declare your application's types.
60
57
  * Add keys inside a `declare module '@abloatai/ablo'` block — for example
61
- * `interface Register { Schema: ...; Presence: ...; }`. It is empty by default,
58
+ * `interface Register { Schema: ...; UserMeta: ...; }`. It is empty by default,
62
59
  * so any key you omit falls back to {@link DefaultSyncShape}. It is exported
63
60
  * from the package root so your augmentation merges into this declaration.
64
61
  *
@@ -77,22 +74,6 @@ export type ResolveSchema = Register extends {
77
74
  } ? S extends {
78
75
  models: Record<string, unknown>;
79
76
  } ? S : DefaultSyncShape['Schema'] : DefaultSyncShape['Schema'];
80
- /**
81
- * Your registered presence shape, or the default when none is registered.
82
- * `usePresence` reads it. The shape is free-form — any JSON-serializable value
83
- * you broadcast per session.
84
- */
85
- export type ResolvePresence = Register extends {
86
- Presence: infer P;
87
- } ? P : DefaultSyncShape['Presence'];
88
- /**
89
- * Your registered claim vocabulary, or the default when none is registered.
90
- * Each key is a claim name and its value is that claim's payload.
91
- * `useClaim(claimName)` reads it.
92
- */
93
- export type ResolveClaims = Register extends {
94
- Claims: infer I;
95
- } ? I : DefaultSyncShape['Claims'];
96
77
  /**
97
78
  * Your registered user-metadata shape, or the default when none is registered.
98
79
  * It carries identity information you trust from your own auth layer; the SDK
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * The single place where you tell the SDK about your application's types.
3
3
  *
4
- * You register your Schema, Presence, Claims, and UserMeta once by augmenting
5
- * the {@link Register} interface. From then on every SDK hook `useAblo`,
6
- * `useQuery`, `useOne`, `usePresence`, `useClaim` reads its types from that
7
- * registration, so you never pass a generic or a `schema` argument at a call
8
- * site.
4
+ * You register your Schema and UserMeta once by augmenting the
5
+ * {@link Register} interface. From then on the typed SDK surface reads its
6
+ * types from that registration, so you never pass a generic or a `schema`
7
+ * argument at a call site.
9
8
  *
10
9
  * Registration uses TypeScript module augmentation: any file in your project
11
10
  * can add an `interface Register` to a `declare module '@abloatai/ablo'`
@@ -27,8 +26,6 @@
27
26
  * declare module '@abloatai/ablo' {
28
27
  * interface Register {
29
28
  * Schema: typeof schema;
30
- * Presence: { cursor: { x: number; y: number } | null };
31
- * Claims: { editLayer: { layerId: string } };
32
29
  * UserMeta: { id: string; email: string };
33
30
  * }
34
31
  * }
@@ -9,8 +9,8 @@
9
9
  * layer underneath.
10
10
  */
11
11
  import type { InferModel, Schema } from '../schema/schema.js';
12
- import type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, PresenceKind, ParticipantKind } from '../coordination/schema.js';
13
- export type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, PresenceKind, ParticipantKind };
12
+ import type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, ClaimLost, PresenceKind, ParticipantKind } from '../coordination/schema.js';
13
+ export type { TargetRange, OnStaleMode, WireClaim, ClaimRejection, ClaimLost, PresenceKind, ParticipantKind, };
14
14
  /**
15
15
  * Any JSON-serializable value. Used where the SDK accepts free-form metadata
16
16
  * that is stored or transported as JSON — more precise than `unknown` while
@@ -384,32 +384,6 @@ export interface ClaimStream {
384
384
  */
385
385
  [Symbol.asyncIterator](): AsyncIterableIterator<readonly Claim[]>;
386
386
  }
387
- /**
388
- * A notification that you lost a claim you were holding — distinct from
389
- * {@link ClaimRejection}, which is a claim the server refused to grant.
390
- * Delivered through `onLost`.
391
- */
392
- export interface ClaimLost {
393
- /** The id of the claim you just lost. */
394
- readonly claimId: string;
395
- /**
396
- * How you lost it. `'preempted'`: a privileged participant (one holding the
397
- * `claim.preempt` capability) evicted you and took the lease — its work now
398
- * supersedes yours, so re-plan against the new holder rather than blindly
399
- * re-claiming. `'expired'`: your TTL lapsed without finishing — re-claim if
400
- * you still need it.
401
- */
402
- readonly reason: 'expired' | 'preempted';
403
- /** The target you no longer hold. */
404
- readonly target: {
405
- readonly entityType: string;
406
- readonly entityId: string;
407
- readonly path?: string;
408
- readonly range?: TargetRange;
409
- readonly field?: string;
410
- readonly meta?: Record<string, unknown>;
411
- };
412
- }
413
387
  /**
414
388
  * Every lifecycle state of a claim. `active` is the current holder — the lock
415
389
  * itself. `queued` is waiting in line behind the holder and carries a