@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.
- package/CHANGELOG.md +10 -0
- package/README.md +2 -1
- package/dist/BaseSyncedStore.d.ts +75 -0
- package/dist/BaseSyncedStore.js +193 -8
- package/dist/Database.d.ts +10 -2
- package/dist/Database.js +15 -1
- package/dist/SyncClient.d.ts +12 -1
- package/dist/SyncClient.js +110 -26
- package/dist/agent/Agent.d.ts +9 -9
- package/dist/agent/Agent.js +16 -16
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.js +1 -1
- package/dist/ai-sdk/{intent-broadcast.d.ts → claim-broadcast.d.ts} +10 -10
- package/dist/ai-sdk/{intent-broadcast.js → claim-broadcast.js} +6 -6
- package/dist/ai-sdk/coordination-context.d.ts +9 -9
- package/dist/ai-sdk/coordination-context.js +8 -8
- package/dist/ai-sdk/index.d.ts +1 -1
- package/dist/ai-sdk/index.js +1 -1
- package/dist/ai-sdk/wrap.d.ts +4 -4
- package/dist/ai-sdk/wrap.js +4 -4
- package/dist/api/index.d.ts +2 -2
- package/dist/cli.cjs +254 -48
- package/dist/client/Ablo.d.ts +30 -63
- package/dist/client/Ablo.js +108 -102
- package/dist/client/ApiClient.d.ts +6 -5
- package/dist/client/ApiClient.js +83 -62
- package/dist/client/createModelProxy.d.ts +16 -54
- package/dist/client/createModelProxy.js +44 -16
- package/dist/client/httpClient.d.ts +2 -0
- package/dist/client/httpClient.js +1 -1
- package/dist/client/index.d.ts +3 -3
- package/dist/client/writeOptionsSchema.d.ts +4 -4
- package/dist/client/writeOptionsSchema.js +4 -4
- package/dist/coordination/schema.d.ts +249 -38
- package/dist/coordination/schema.js +172 -39
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +4 -4
- package/dist/errorCodes.d.ts +9 -9
- package/dist/errorCodes.js +15 -15
- package/dist/errors.d.ts +51 -2
- package/dist/errors.js +94 -5
- package/dist/interfaces/index.d.ts +8 -4
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/types.d.ts +13 -13
- package/dist/policy/types.js +8 -8
- package/dist/react/AbloProvider.d.ts +51 -4
- package/dist/react/AbloProvider.js +95 -11
- package/dist/react/context.d.ts +26 -9
- package/dist/react/context.js +2 -2
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +4 -4
- package/dist/react/useAblo.js +5 -5
- package/dist/react/{useIntent.d.ts → useClaim.d.ts} +9 -9
- package/dist/react/useClaim.js +42 -0
- package/dist/schema/index.js +1 -1
- package/dist/schema/sugar.d.ts +3 -3
- package/dist/schema/sugar.js +3 -3
- package/dist/schema/sync-delta-wire.d.ts +8 -8
- package/dist/server/commit.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.d.ts +162 -0
- package/dist/sync/AreaOfInterestManager.js +233 -0
- package/dist/sync/BootstrapHelper.d.ts +9 -1
- package/dist/sync/BootstrapHelper.js +15 -5
- package/dist/sync/NetworkProbe.d.ts +1 -1
- package/dist/sync/NetworkProbe.js +1 -1
- package/dist/sync/SyncWebSocket.d.ts +59 -25
- package/dist/sync/SyncWebSocket.js +123 -26
- package/dist/sync/awaitClaimGrant.d.ts +40 -0
- package/dist/sync/awaitClaimGrant.js +86 -0
- package/dist/sync/createClaimStream.d.ts +34 -0
- package/dist/sync/{createIntentStream.js → createClaimStream.js} +92 -81
- package/dist/sync/createPresenceStream.js +3 -2
- package/dist/sync/participants.d.ts +10 -10
- package/dist/sync/participants.js +17 -10
- package/dist/sync/schemas.d.ts +8 -8
- package/dist/transactions/TransactionQueue.d.ts +12 -0
- package/dist/transactions/TransactionQueue.js +126 -8
- package/dist/types/global.d.ts +10 -10
- package/dist/types/global.js +3 -3
- package/dist/types/index.d.ts +9 -7
- package/dist/types/index.js +2 -2
- package/dist/types/streams.d.ts +114 -98
- package/dist/types/streams.js +1 -1
- package/dist/utils/asyncIterator.d.ts +1 -1
- package/dist/utils/asyncIterator.js +1 -1
- package/dist/wire/frames.d.ts +2 -2
- package/package.json +3 -2
- package/dist/react/useIntent.js +0 -42
- package/dist/sync/awaitIntentGrant.d.ts +0 -40
- package/dist/sync/awaitIntentGrant.js +0 -62
- 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/
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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 `
|
|
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 (
|
|
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.
|
|
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.
|
|
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 ? {
|
|
303
|
-
...(isClaimHandle(claim) ? {
|
|
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
|
-
|
|
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 ? {
|
|
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
|
-
|
|
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 ? {
|
|
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`, `
|
|
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
|
*/
|
package/dist/client/index.d.ts
CHANGED
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
* });
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
export { Ablo, computeFKDepthPriority, type AbloOptions, type InternalAbloOptions, type ClaimedOptions, type IfClaimedPolicy, type
|
|
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,
|
|
36
|
-
export type { EngineParticipant, JoinedParticipant, ParticipantJoinOptions, ParticipantManager, ParticipantScope, ParticipantStatus,
|
|
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 `
|
|
14
|
-
* carries live handles (`
|
|
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
|
-
|
|
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 `
|
|
14
|
-
* carries live handles (`
|
|
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/
|
|
31
|
+
/** Claim/claim attribution — an id, or a live lease handle (loose: the
|
|
32
32
|
* handle's `release`/`revoke` functions ride along untouched). */
|
|
33
|
-
|
|
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
|
});
|