@abloatai/ablo 0.10.1 → 0.11.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.
- package/CHANGELOG.md +34 -0
- package/README.md +63 -23
- 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 +369 -67
- package/dist/client/Ablo.d.ts +30 -63
- package/dist/client/Ablo.js +124 -103
- package/dist/client/ApiClient.d.ts +6 -5
- package/dist/client/ApiClient.js +86 -62
- package/dist/client/auth.d.ts +9 -4
- package/dist/client/auth.js +40 -5
- package/dist/client/createModelProxy.d.ts +41 -54
- package/dist/client/createModelProxy.js +123 -20
- 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 +16 -16
- 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/schema.d.ts +3 -3
- 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 +23 -0
- package/dist/transactions/TransactionQueue.js +186 -12
- package/dist/types/global.d.ts +18 -13
- package/dist/types/global.js +11 -6
- 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/docs/api.md +3 -3
- package/docs/client-behavior.md +6 -3
- package/docs/coordination.md +13 -3
- package/docs/data-sources.md +29 -9
- package/docs/migration.md +40 -0
- package/docs/quickstart.md +61 -33
- package/docs/react.md +46 -0
- package/llms-full.txt +25 -8
- package/llms.txt +11 -9
- 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,8 +15,10 @@
|
|
|
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';
|
|
21
|
+
import { toMs } from '../utils/duration.js';
|
|
20
22
|
import { assertWriteOptions } from './writeOptionsSchema.js';
|
|
21
23
|
import { ModelScope } from '../types/index.js';
|
|
22
24
|
const modelClientMeta = new WeakMap();
|
|
@@ -31,9 +33,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
31
33
|
throw new AbloValidationError(`Ablo: schema model "${schemaKey}" resolved to "${registeredModelName}", ` +
|
|
32
34
|
'but no matching constructor was registered.', { code: 'model_not_registered' });
|
|
33
35
|
}
|
|
34
|
-
// The coordination plane (claims/
|
|
36
|
+
// The coordination plane (claims/claims) must speak the SAME wire dialect
|
|
35
37
|
// as the commit plane: the lowercased TYPENAME (`task`), not the schema key
|
|
36
|
-
// (`tasks`). The server's commit-time
|
|
38
|
+
// (`tasks`). The server's commit-time claim guard probes the lease store
|
|
37
39
|
// with the commit op's model name; a lease recorded under the schema key
|
|
38
40
|
// never collides with it — which silently disarmed the guard for every
|
|
39
41
|
// model whose schema key differs from its typename (i.e. nearly all of
|
|
@@ -74,7 +76,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
74
76
|
// Claims this proxy currently holds, keyed by entity id. Lets the flat
|
|
75
77
|
// `release({ id })` and `update({ id, data })` find the lease + snapshot a `claim({ id })`
|
|
76
78
|
// took — no per-call handle. Released on dispose, explicit release, or TTL.
|
|
79
|
+
//
|
|
80
|
+
// `target` / `action` / `expiresAt` are kept alongside the lease so
|
|
81
|
+
// `claim.state` can synthesize a self-claim: the server excludes a holder's
|
|
82
|
+
// own presence frames, so the local proxy is the ONLY place that knows "I
|
|
83
|
+
// hold this." `expiresAt` is the client's best estimate from the requested
|
|
84
|
+
// TTL (a genuine epoch-ms expiry, not a fabricated watermark), defaulting to
|
|
85
|
+
// the server's keepalive lease window when no TTL was requested.
|
|
77
86
|
const activeClaims = new Map();
|
|
87
|
+
// Server keepalive lease window (Hub `LEASE_RENEW_TTL_MS`). The fallback
|
|
88
|
+
// expiry estimate when a claim is taken without an explicit TTL.
|
|
89
|
+
const DEFAULT_LEASE_TTL_MS = 90_000;
|
|
78
90
|
const isClaimHandle = (value) => typeof value === 'object' &&
|
|
79
91
|
value !== null &&
|
|
80
92
|
value.object === 'claim' &&
|
|
@@ -85,6 +97,27 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
85
97
|
return options?.meta;
|
|
86
98
|
return { ...(options.meta ?? {}), description: options.description };
|
|
87
99
|
};
|
|
100
|
+
const claimContextFromClaim = (claim) => {
|
|
101
|
+
const description = claim.description ?? descriptionFromMeta(claim.target.meta);
|
|
102
|
+
return {
|
|
103
|
+
id: claim.id,
|
|
104
|
+
actor: claim.heldBy,
|
|
105
|
+
participantKind: claim.participantKind,
|
|
106
|
+
action: claim.action,
|
|
107
|
+
...(description ? { description } : {}),
|
|
108
|
+
field: claim.target.field,
|
|
109
|
+
status: claim.status,
|
|
110
|
+
expiresAt: claim.expiresAt,
|
|
111
|
+
target: {
|
|
112
|
+
model: claim.target.type,
|
|
113
|
+
id: claim.target.id,
|
|
114
|
+
path: claim.target.path,
|
|
115
|
+
range: claim.target.range,
|
|
116
|
+
field: claim.target.field,
|
|
117
|
+
meta: claim.target.meta,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
};
|
|
88
121
|
const mutationOptions = (params) => {
|
|
89
122
|
const { id: _id, data: _data, claim: _claim, ...rest } = params;
|
|
90
123
|
// THE write-options schema — runtime twin of the compile-time params.
|
|
@@ -102,7 +135,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
102
135
|
};
|
|
103
136
|
const takeClaim = async (params) => {
|
|
104
137
|
if (!collaboration) {
|
|
105
|
-
throw new AbloValidationError(`Model "${schemaKey}"
|
|
138
|
+
throw new AbloValidationError(`Model "${schemaKey}" was built without the collaboration runtime, so claim() is unavailable here. Claiming needs no per-model config — use the standard Ablo({ schema, apiKey }) client and every model is claimable.`, { code: 'model_claim_not_configured' });
|
|
106
139
|
}
|
|
107
140
|
const { id, ...options } = params;
|
|
108
141
|
// Is someone ELSE already on this target? Read the local coordination
|
|
@@ -114,11 +147,17 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
114
147
|
// Fail-fast (`wait: false`): if another participant already holds it,
|
|
115
148
|
// reject now instead of queuing. Best-effort at the client (a racing
|
|
116
149
|
// claim not yet synced into our snapshot slips through here) — the
|
|
117
|
-
// commit-time
|
|
150
|
+
// commit-time claim guard is the authoritative backstop that rejects
|
|
118
151
|
// the loser's first write. For work-distribution dedup that's exactly
|
|
119
152
|
// right: don't wait (that would double-process), skip.
|
|
120
153
|
if (failFast && contended) {
|
|
121
|
-
|
|
154
|
+
const claim = held ? claimContextFromClaim(held) : undefined;
|
|
155
|
+
throw new AbloClaimedError(formatClaimedErrorMessage({
|
|
156
|
+
targetLabel: `${registeredModelName}/${id}`,
|
|
157
|
+
heldBy: held?.heldBy,
|
|
158
|
+
claim,
|
|
159
|
+
fallback: `${registeredModelName}/${id} is held by ${held?.heldBy ?? 'another participant'}.`,
|
|
160
|
+
}), { code: 'entity_claimed', claims: claim ? [claim] : undefined });
|
|
122
161
|
}
|
|
123
162
|
// Ensure the row exists locally before claiming.
|
|
124
163
|
let model = objectPool.get(id);
|
|
@@ -129,12 +168,20 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
129
168
|
if (!model) {
|
|
130
169
|
throw new AbloValidationError(`Entity not found: ${registeredModelName}/${id}`, { code: 'entity_not_found' });
|
|
131
170
|
}
|
|
171
|
+
// Write-intent: enter the entity scope BEFORE acquiring the lease so the
|
|
172
|
+
// holder's claim presence broadcasts to whoever is in this entity group —
|
|
173
|
+
// including a peer that subscribed just before us. Pinning before the
|
|
174
|
+
// lease (rather than after) closes the subscribe-vs-broadcast race: the
|
|
175
|
+
// server fans `broadcastPresenceChange` out at claim time, so we must be
|
|
176
|
+
// in the group when `createClaim` lands. Awaited because the broadcast
|
|
177
|
+
// ordering depends on it; still soft (the store swallows reconcile errors).
|
|
178
|
+
await collaboration.pinScope?.({ [schemaKey]: id });
|
|
132
179
|
// Acquire the lease. Default (`wait` !== false) goes through the server's
|
|
133
180
|
// fair FIFO queue — `queue: true` resolves only once the lease is genuinely
|
|
134
181
|
// ours, blocking behind any current holder, with no TOCTOU gap (the server
|
|
135
182
|
// orders contenders). Fail-fast skips the queue: we already rejected an
|
|
136
183
|
// observed conflict above, so this just records our lease.
|
|
137
|
-
const lease = await collaboration.
|
|
184
|
+
const lease = await collaboration.createClaim({
|
|
138
185
|
target: {
|
|
139
186
|
model: wireModel,
|
|
140
187
|
id,
|
|
@@ -151,9 +198,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
151
198
|
// Only when we actually waited behind another holder can the row have
|
|
152
199
|
// changed underneath us — re-read so the claimed snapshot reflects what
|
|
153
200
|
// they committed before releasing. Two signals, either suffices:
|
|
154
|
-
// - `lease.waited` — the server granted via `
|
|
201
|
+
// - `lease.waited` — the server granted via `claim_granted`, i.e. we
|
|
155
202
|
// provably queued behind a holder. Authoritative; works even when
|
|
156
|
-
// the local snapshot is blind (
|
|
203
|
+
// the local snapshot is blind (claim fan-out is entity-scoped, so
|
|
157
204
|
// org-wide-subscribed clients never observe peers' claims).
|
|
158
205
|
// - `contended` — the local snapshot saw a holder up front. Kept for
|
|
159
206
|
// the no-queue paths where no grant frame exists.
|
|
@@ -166,7 +213,27 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
166
213
|
model = objectPool.get(id) ?? model;
|
|
167
214
|
}
|
|
168
215
|
const snapshot = collaboration.createSnapshot(schemaKey, id);
|
|
169
|
-
|
|
216
|
+
const action = options?.action ?? 'editing';
|
|
217
|
+
// The self-claim's `EntityRef` mirrors what a peer's `claim.state` would
|
|
218
|
+
// report (`observe` maps `held.target.model` → `type`), so a holder and a
|
|
219
|
+
// peer see the SAME target.type for one row — the wire model token.
|
|
220
|
+
const selfTarget = {
|
|
221
|
+
type: wireModel,
|
|
222
|
+
id,
|
|
223
|
+
...(options?.field ? { field: options.field } : {}),
|
|
224
|
+
...(options?.path ? { path: options.path } : {}),
|
|
225
|
+
...(options?.range ? { range: options.range } : {}),
|
|
226
|
+
...(claimMeta(options) ? { meta: claimMeta(options) } : {}),
|
|
227
|
+
};
|
|
228
|
+
const expiresAt = Date.now() +
|
|
229
|
+
(options?.ttl !== undefined ? toMs(options.ttl) : DEFAULT_LEASE_TTL_MS);
|
|
230
|
+
activeClaims.set(id, {
|
|
231
|
+
lease,
|
|
232
|
+
snapshot,
|
|
233
|
+
target: selfTarget,
|
|
234
|
+
action,
|
|
235
|
+
expiresAt,
|
|
236
|
+
});
|
|
170
237
|
const target = {
|
|
171
238
|
model: schemaKey,
|
|
172
239
|
id,
|
|
@@ -178,10 +245,10 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
178
245
|
const release = () => releaseClaim(id);
|
|
179
246
|
return {
|
|
180
247
|
object: 'claim',
|
|
181
|
-
claimId: lease.
|
|
248
|
+
claimId: lease.claimId,
|
|
182
249
|
readAt: snapshot.stamp,
|
|
183
250
|
target,
|
|
184
|
-
action
|
|
251
|
+
action,
|
|
185
252
|
...(options?.description ? { description: options.description } : {}),
|
|
186
253
|
data: modelAsRow(model),
|
|
187
254
|
release,
|
|
@@ -198,6 +265,28 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
198
265
|
// are the same object.
|
|
199
266
|
const claimApi = Object.assign(guard(claim), {
|
|
200
267
|
state(params) {
|
|
268
|
+
// Read-interest: a passive observer subscribing to a row's claim state
|
|
269
|
+
// must enter that row's entity scope, or it sits on `org:`/`user:`
|
|
270
|
+
// groups only and never receives the holder's entity-scoped claim
|
|
271
|
+
// presence. Soft + fire-and-forget — never blocks or rejects the read.
|
|
272
|
+
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
273
|
+
// Self-awareness: the server excludes a holder's OWN presence frames and
|
|
274
|
+
// the client skips them, so `observe` returns null for a row WE hold.
|
|
275
|
+
// Synthesize the active claim for self from the stored lease so the
|
|
276
|
+
// holder sees its own claim (the JSDoc contract on `claim.state`).
|
|
277
|
+
const own = activeClaims.get(params.id);
|
|
278
|
+
if (own) {
|
|
279
|
+
return {
|
|
280
|
+
object: 'claim',
|
|
281
|
+
id: own.lease.claimId,
|
|
282
|
+
status: 'active',
|
|
283
|
+
target: own.target,
|
|
284
|
+
action: own.action,
|
|
285
|
+
heldBy: collaboration?.selfParticipantId ?? '',
|
|
286
|
+
participantKind: collaboration?.selfParticipantKind ?? 'user',
|
|
287
|
+
expiresAt: own.expiresAt,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
201
290
|
return collaboration?.observe({ model: wireModel, id: params.id }) ?? null;
|
|
202
291
|
},
|
|
203
292
|
queue(params) {
|
|
@@ -213,6 +302,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
213
302
|
});
|
|
214
303
|
const operations = {
|
|
215
304
|
retrieve: guard(async (params) => {
|
|
305
|
+
// Read-interest enrolment: READ a row → enter its entity scope, so a
|
|
306
|
+
// Node/agent client lands in the same group the holder's claim presence
|
|
307
|
+
// fans out on and `claim.state`/`claim.queue` report peers. Soft +
|
|
308
|
+
// fire-and-forget — never make the read reject or slower.
|
|
309
|
+
void collaboration?.enterScope?.({ [schemaKey]: params.id });
|
|
216
310
|
const rows = await load({
|
|
217
311
|
...params,
|
|
218
312
|
where: [['id', params.id]],
|
|
@@ -220,6 +314,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
220
314
|
});
|
|
221
315
|
return rows[0];
|
|
222
316
|
}),
|
|
317
|
+
// NB: no auto scope enrolment on bulk `list`/`getAll` — that would
|
|
318
|
+
// subscribe to an unbounded set of rows' entity groups. Bulk-list scope
|
|
319
|
+
// enrolment is a deliberate follow-up (a bounded, opt-in policy).
|
|
223
320
|
list: guard(load),
|
|
224
321
|
get(id) {
|
|
225
322
|
return objectPool.get(id);
|
|
@@ -267,9 +364,15 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
267
364
|
let autoLease;
|
|
268
365
|
if (claim && !isClaimHandle(claim)) {
|
|
269
366
|
if (!collaboration) {
|
|
270
|
-
throw new AbloValidationError(`Model "${schemaKey}"
|
|
367
|
+
throw new AbloValidationError(`Model "${schemaKey}" was built without the collaboration runtime, so claim() is unavailable here. Claiming needs no per-model config — use the standard Ablo({ schema, apiKey }) client and every model is claimable.`, { code: 'model_claim_not_configured' });
|
|
271
368
|
}
|
|
272
|
-
|
|
369
|
+
// Write-intent: enter the new row's entity scope BEFORE acquiring the
|
|
370
|
+
// create-claim so the holder's claim presence broadcasts to whoever is
|
|
371
|
+
// already in this entity group (closing the subscribe-vs-broadcast
|
|
372
|
+
// race — see `takeClaim`). Released with the lease in the `finally`
|
|
373
|
+
// below. Awaited for broadcast ordering; still soft.
|
|
374
|
+
await collaboration.pinScope?.({ [schemaKey]: id });
|
|
375
|
+
autoLease = await collaboration.createClaim({
|
|
273
376
|
target: {
|
|
274
377
|
model: wireModel,
|
|
275
378
|
id,
|
|
@@ -299,8 +402,8 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
299
402
|
});
|
|
300
403
|
const effective = {
|
|
301
404
|
...opts,
|
|
302
|
-
...(autoLease ? {
|
|
303
|
-
...(isClaimHandle(claim) ? {
|
|
405
|
+
...(autoLease ? { claim: autoLease } : {}),
|
|
406
|
+
...(isClaimHandle(claim) ? { claim: { id: claim.claimId } } : {}),
|
|
304
407
|
};
|
|
305
408
|
try {
|
|
306
409
|
syncClient.add(model, effective);
|
|
@@ -336,7 +439,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
336
439
|
wait: 'confirmed',
|
|
337
440
|
readAt: claimed.snapshot.stamp,
|
|
338
441
|
onStale: 'reject',
|
|
339
|
-
|
|
442
|
+
claimRef: { id: claimed.lease.claimId },
|
|
340
443
|
...opts,
|
|
341
444
|
}
|
|
342
445
|
: {
|
|
@@ -351,7 +454,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
351
454
|
}
|
|
352
455
|
: {}),
|
|
353
456
|
...opts,
|
|
354
|
-
...(handle ? {
|
|
457
|
+
...(handle ? { claim: { id: handle.claimId } } : {}),
|
|
355
458
|
};
|
|
356
459
|
// Local user update: `applyChanges` keeps change tracking ON so
|
|
357
460
|
// the edited fields land in `modifiedProperties` and actually get
|
|
@@ -386,7 +489,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
386
489
|
wait: 'confirmed',
|
|
387
490
|
readAt: claimed.snapshot.stamp,
|
|
388
491
|
onStale: 'reject',
|
|
389
|
-
|
|
492
|
+
claimRef: { id: claimed.lease.claimId },
|
|
390
493
|
...opts,
|
|
391
494
|
}
|
|
392
495
|
: {
|
|
@@ -398,7 +501,7 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
|
|
|
398
501
|
}
|
|
399
502
|
: {}),
|
|
400
503
|
...opts,
|
|
401
|
-
...(handle ? {
|
|
504
|
+
...(handle ? { claim: { id: handle.claimId } } : {}),
|
|
402
505
|
};
|
|
403
506
|
syncClient.delete(model, effective);
|
|
404
507
|
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
|
});
|