@abloatai/ablo 0.5.0 → 0.6.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 +22 -0
- package/README.md +242 -135
- package/dist/BaseSyncedStore.d.ts +2 -2
- package/dist/BaseSyncedStore.js +2 -2
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.js +1 -1
- package/dist/client/Ablo.d.ts +90 -93
- package/dist/client/Ablo.js +121 -60
- package/dist/client/ApiClient.d.ts +14 -14
- package/dist/client/ApiClient.js +81 -55
- package/dist/client/createInternalComponents.d.ts +2 -3
- package/dist/client/createInternalComponents.js +2 -3
- package/dist/client/createModelProxy.d.ts +90 -87
- package/dist/client/createModelProxy.js +124 -127
- package/dist/client/index.d.ts +6 -7
- package/dist/client/index.js +4 -5
- package/dist/client/validateAbloOptions.js +3 -3
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +7 -0
- package/dist/errors.d.ts +8 -8
- package/dist/errors.js +18 -10
- package/dist/index.d.ts +9 -8
- package/dist/index.js +7 -11
- package/dist/interfaces/index.d.ts +2 -10
- package/dist/mutators/Transaction.d.ts +2 -2
- package/dist/mutators/Transaction.js +2 -2
- package/dist/mutators/mutateActions.d.ts +44 -0
- package/dist/{react/useMutate.js → mutators/mutateActions.js} +11 -28
- package/dist/mutators/readerActions.d.ts +32 -0
- package/dist/{react/useReader.js → mutators/readerActions.js} +2 -18
- package/dist/query/types.d.ts +1 -1
- package/dist/react/AbloProvider.d.ts +1 -1
- package/dist/react/AbloProvider.js +3 -3
- package/dist/react/context.d.ts +4 -4
- package/dist/react/index.d.ts +4 -5
- package/dist/react/index.js +3 -7
- package/dist/react/useAblo.d.ts +14 -14
- package/dist/react/useAblo.js +26 -26
- package/dist/react/useIntent.d.ts +2 -2
- package/dist/react/useIntent.js +2 -2
- package/dist/react/useMutators.d.ts +1 -1
- package/dist/react/usePresence.d.ts +3 -3
- package/dist/react/usePresence.js +4 -4
- package/dist/react/useUndoScope.d.ts +1 -1
- package/dist/schema/diff.d.ts +161 -0
- package/dist/schema/diff.js +262 -0
- package/dist/schema/generate.d.ts +19 -0
- package/dist/schema/generate.js +87 -0
- package/dist/schema/index.d.ts +4 -1
- package/dist/schema/index.js +7 -1
- package/dist/schema/schema.d.ts +83 -32
- package/dist/schema/schema.js +58 -12
- package/dist/schema/serialize.d.ts +92 -0
- package/dist/schema/serialize.js +227 -0
- package/dist/sync/SyncWebSocket.d.ts +17 -0
- package/dist/sync/SyncWebSocket.js +46 -1
- package/dist/sync/awaitIntentGrant.d.ts +26 -0
- package/dist/sync/awaitIntentGrant.js +60 -0
- package/dist/sync/createIntentStream.js +43 -4
- package/dist/sync/createPresenceStream.js +1 -1
- package/dist/sync/participants.d.ts +2 -2
- package/dist/sync/participants.js +4 -4
- package/dist/types/global.d.ts +43 -52
- package/dist/types/global.js +16 -18
- package/dist/types/streams.d.ts +37 -9
- package/docs/api.md +68 -158
- package/docs/audit.md +5 -5
- package/docs/client-behavior.md +41 -42
- package/docs/coordination.md +294 -0
- package/docs/data-sources.md +14 -14
- package/docs/examples/agent-human.md +30 -32
- package/docs/examples/ai-sdk-tool.md +32 -33
- package/docs/examples/existing-python-backend.md +35 -33
- package/docs/examples/nextjs.md +24 -25
- package/docs/examples/server-agent.md +20 -61
- package/docs/guarantees.md +30 -55
- package/docs/identity.md +458 -0
- package/docs/index.md +12 -24
- package/docs/integration-guide.md +106 -116
- package/docs/interaction-model.md +29 -95
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +1 -1
- package/docs/mcp.md +11 -26
- package/docs/quickstart.md +43 -49
- package/docs/react.md +73 -23
- package/docs/roadmap.md +5 -7
- package/llms.txt +34 -39
- package/package.json +1 -1
- package/dist/react/useMutate.d.ts +0 -83
- package/dist/react/useQuery.d.ts +0 -123
- package/dist/react/useQuery.js +0 -145
- package/dist/react/useReader.d.ts +0 -69
- package/docs/capabilities.md +0 -163
package/dist/client/Ablo.js
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
*
|
|
11
11
|
* const sync = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
12
12
|
*
|
|
13
|
-
* const
|
|
14
|
-
* await sync.
|
|
15
|
-
* await sync.
|
|
16
|
-
* await sync.
|
|
13
|
+
* const reports = sync.reports.list({ where: { status: 'todo' } });
|
|
14
|
+
* await sync.reports.create({ title: 'Fix bug' });
|
|
15
|
+
* await sync.reports.update(reportId, { status: 'ready' });
|
|
16
|
+
* await sync.reports.delete(reportId);
|
|
17
17
|
*/
|
|
18
18
|
import { z } from 'zod';
|
|
19
|
-
import {
|
|
19
|
+
import { AbloClaimedError, AbloError, AbloConnectionError, AbloValidationError, translateHttpError } from '../errors.js';
|
|
20
20
|
import { LoadStrategy, PropertyType } from '../types/index.js';
|
|
21
21
|
import { initSyncEngine } from '../context.js';
|
|
22
22
|
import { noopObservability, browserOnlineStatus, defaultSessionErrorDetector, noopAnalytics, } from '../SyncEngineContext.js';
|
|
@@ -28,6 +28,7 @@ import { Model } from '../Model.js';
|
|
|
28
28
|
import { BaseSyncedStore } from '../BaseSyncedStore.js';
|
|
29
29
|
import { createPresenceStream } from '../sync/createPresenceStream.js';
|
|
30
30
|
import { createIntentStream } from '../sync/createIntentStream.js';
|
|
31
|
+
import { awaitIntentGrant } from '../sync/awaitIntentGrant.js';
|
|
31
32
|
import { createSnapshot } from '../sync/createSnapshot.js';
|
|
32
33
|
import { createParticipantManager } from '../sync/participants.js';
|
|
33
34
|
import { createProtocolClient, } from './ApiClient.js';
|
|
@@ -581,7 +582,8 @@ function createDefaultMutationExecutor(getWs) {
|
|
|
581
582
|
? crypto.randomUUID()
|
|
582
583
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`);
|
|
583
584
|
try {
|
|
584
|
-
return await ws.sendCommit(operations, clientTxId,
|
|
585
|
+
return await ws.sendCommit(operations, clientTxId, undefined, // use sendCommit's built-in 15s default; no per-call override
|
|
586
|
+
options?.causedByTaskId);
|
|
585
587
|
}
|
|
586
588
|
catch (err) {
|
|
587
589
|
// Wrap transport-level failures as connection errors so the
|
|
@@ -965,7 +967,7 @@ export function Ablo(options) {
|
|
|
965
967
|
? crypto.randomUUID()
|
|
966
968
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
967
969
|
}
|
|
968
|
-
function
|
|
970
|
+
function createModelId() {
|
|
969
971
|
return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
970
972
|
? crypto.randomUUID()
|
|
971
973
|
: `id_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -976,15 +978,15 @@ export function Ablo(options) {
|
|
|
976
978
|
return intent?.id;
|
|
977
979
|
}
|
|
978
980
|
function normalizeCommitOperation(op, defaults) {
|
|
979
|
-
const
|
|
980
|
-
if (!
|
|
981
|
-
throw new AbloValidationError('Commit operation requires `
|
|
981
|
+
const model = op.model ?? op.target?.model;
|
|
982
|
+
if (!model) {
|
|
983
|
+
throw new AbloValidationError('Commit operation requires `model` or `target.model`.', { code: 'commit_operation_model_required' });
|
|
982
984
|
}
|
|
983
985
|
const type = op.action.toUpperCase();
|
|
984
986
|
const id = op.id ?? op.target?.id ?? '';
|
|
985
987
|
return {
|
|
986
988
|
type,
|
|
987
|
-
model:
|
|
989
|
+
model: model.toLowerCase(),
|
|
988
990
|
id,
|
|
989
991
|
input: op.data ?? undefined,
|
|
990
992
|
transactionId: op.transactionId ?? undefined,
|
|
@@ -1004,16 +1006,17 @@ export function Ablo(options) {
|
|
|
1004
1006
|
}
|
|
1005
1007
|
return inputOperations.map((op) => normalizeCommitOperation(op, commitOptions));
|
|
1006
1008
|
}
|
|
1007
|
-
function
|
|
1009
|
+
function modelClaimFromActive(intent) {
|
|
1008
1010
|
return {
|
|
1009
1011
|
id: intent.id,
|
|
1010
1012
|
actor: intent.heldBy,
|
|
1011
1013
|
participantKind: intent.participantKind,
|
|
1012
1014
|
action: intent.reason,
|
|
1013
1015
|
field: intent.target.field,
|
|
1016
|
+
status: 'active',
|
|
1014
1017
|
expiresAt: intent.expiresAt,
|
|
1015
1018
|
target: {
|
|
1016
|
-
|
|
1019
|
+
model: intent.target.type,
|
|
1017
1020
|
id: intent.target.id,
|
|
1018
1021
|
path: intent.target.path,
|
|
1019
1022
|
range: intent.target.range,
|
|
@@ -1022,9 +1025,29 @@ export function Ablo(options) {
|
|
|
1022
1025
|
},
|
|
1023
1026
|
};
|
|
1024
1027
|
}
|
|
1025
|
-
function
|
|
1026
|
-
|
|
1027
|
-
intent.
|
|
1028
|
+
function modelClaimFromQueued(intent) {
|
|
1029
|
+
return {
|
|
1030
|
+
id: intent.id,
|
|
1031
|
+
actor: intent.heldBy,
|
|
1032
|
+
participantKind: intent.participantKind,
|
|
1033
|
+
action: intent.action,
|
|
1034
|
+
field: intent.target.field,
|
|
1035
|
+
status: 'queued',
|
|
1036
|
+
position: intent.position,
|
|
1037
|
+
expiresAt: intent.expiresAt,
|
|
1038
|
+
target: {
|
|
1039
|
+
model: intent.target.type,
|
|
1040
|
+
id: intent.target.id,
|
|
1041
|
+
path: intent.target.path,
|
|
1042
|
+
range: intent.target.range,
|
|
1043
|
+
field: intent.target.field,
|
|
1044
|
+
meta: intent.target.meta,
|
|
1045
|
+
},
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function targetMatchesModel(target, intent) {
|
|
1049
|
+
if (target.model &&
|
|
1050
|
+
intent.target.type.toLowerCase() !== target.model.toLowerCase()) {
|
|
1028
1051
|
return false;
|
|
1029
1052
|
}
|
|
1030
1053
|
if (target.id && intent.target.id !== target.id)
|
|
@@ -1033,21 +1056,29 @@ export function Ablo(options) {
|
|
|
1033
1056
|
return false;
|
|
1034
1057
|
return true;
|
|
1035
1058
|
}
|
|
1036
|
-
function
|
|
1059
|
+
function listModelClaims(target) {
|
|
1037
1060
|
return intentStream.others
|
|
1038
|
-
.filter((intent) => (target ?
|
|
1039
|
-
.map(
|
|
1061
|
+
.filter((intent) => (target ? targetMatchesModel(target, intent) : true))
|
|
1062
|
+
.map(modelClaimFromActive);
|
|
1040
1063
|
}
|
|
1041
|
-
function
|
|
1042
|
-
|
|
1043
|
-
|
|
1064
|
+
function listModelClaimQueue(target) {
|
|
1065
|
+
if (!target?.model || !target.id)
|
|
1066
|
+
return [];
|
|
1067
|
+
return publicIntents
|
|
1068
|
+
.queueFor({ type: target.model, id: target.id })
|
|
1069
|
+
.filter((intent) => (target.field ? intent.target.field === target.field : true))
|
|
1070
|
+
.map(modelClaimFromQueued);
|
|
1071
|
+
}
|
|
1072
|
+
function claimedError(target, claims, code) {
|
|
1073
|
+
const label = [target.model, target.id, target.field].filter(Boolean).join('/');
|
|
1074
|
+
const holder = claims[0];
|
|
1044
1075
|
const suffix = holder
|
|
1045
1076
|
? ` held by ${holder.actor} (${holder.action})`
|
|
1046
1077
|
: ' held by another participant';
|
|
1047
|
-
return new
|
|
1078
|
+
return new AbloClaimedError(`Model row is claimed: ${label || 'target'}${suffix}.`, { code, claims });
|
|
1048
1079
|
}
|
|
1049
|
-
function
|
|
1050
|
-
if (
|
|
1080
|
+
function waitForModelUnclaimed(target, options) {
|
|
1081
|
+
if (listModelClaims(target).length === 0)
|
|
1051
1082
|
return Promise.resolve();
|
|
1052
1083
|
return new Promise((resolve, reject) => {
|
|
1053
1084
|
let settled = false;
|
|
@@ -1068,7 +1099,7 @@ export function Ablo(options) {
|
|
|
1068
1099
|
fn();
|
|
1069
1100
|
};
|
|
1070
1101
|
const check = () => {
|
|
1071
|
-
if (
|
|
1102
|
+
if (listModelClaims(target).length === 0) {
|
|
1072
1103
|
finish(resolve);
|
|
1073
1104
|
}
|
|
1074
1105
|
};
|
|
@@ -1082,25 +1113,30 @@ export function Ablo(options) {
|
|
|
1082
1113
|
onAbort();
|
|
1083
1114
|
return;
|
|
1084
1115
|
}
|
|
1085
|
-
unsubscribe = intentStream.
|
|
1116
|
+
unsubscribe = intentStream.onChange(check);
|
|
1086
1117
|
options?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
1087
1118
|
if (options?.timeout != null) {
|
|
1088
1119
|
timeoutId = setTimeout(() => {
|
|
1089
|
-
finish(() => reject(
|
|
1120
|
+
finish(() => reject(claimedError(target, listModelClaims(target), 'model_claimed_timeout')));
|
|
1090
1121
|
}, options.timeout);
|
|
1091
1122
|
}
|
|
1092
1123
|
});
|
|
1093
1124
|
}
|
|
1094
|
-
async function
|
|
1095
|
-
const policy = options?.
|
|
1125
|
+
async function applyClaimedPolicy(target, options) {
|
|
1126
|
+
const policy = options?.ifClaimed ?? 'return';
|
|
1096
1127
|
if (policy === 'return')
|
|
1097
1128
|
return;
|
|
1098
|
-
const current =
|
|
1129
|
+
const current = listModelClaims(target);
|
|
1099
1130
|
if (current.length === 0)
|
|
1100
1131
|
return;
|
|
1101
1132
|
if (policy === 'fail')
|
|
1102
|
-
throw
|
|
1103
|
-
|
|
1133
|
+
throw claimedError(target, current, 'model_claimed');
|
|
1134
|
+
const queue = listModelClaimQueue(target);
|
|
1135
|
+
if (options?.maxQueueDepth !== undefined &&
|
|
1136
|
+
queue.length >= options.maxQueueDepth) {
|
|
1137
|
+
throw claimedError(target, current, 'queue_too_deep');
|
|
1138
|
+
}
|
|
1139
|
+
await waitForModelUnclaimed(target, { timeout: options?.claimedTimeout });
|
|
1104
1140
|
}
|
|
1105
1141
|
function wrapIntentHandle(claim) {
|
|
1106
1142
|
const release = async () => {
|
|
@@ -1117,24 +1153,51 @@ export function Ablo(options) {
|
|
|
1117
1153
|
async create(intentOptions) {
|
|
1118
1154
|
await ready();
|
|
1119
1155
|
const claim = intentStream.claim({
|
|
1120
|
-
type: intentOptions.target.
|
|
1156
|
+
type: intentOptions.target.model,
|
|
1121
1157
|
id: intentOptions.target.id,
|
|
1122
1158
|
path: intentOptions.target.path,
|
|
1123
1159
|
range: intentOptions.target.range,
|
|
1124
1160
|
field: intentOptions.target.field,
|
|
1125
1161
|
meta: intentOptions.target.meta,
|
|
1126
|
-
}, {
|
|
1162
|
+
}, {
|
|
1163
|
+
reason: intentOptions.action,
|
|
1164
|
+
ttl: intentOptions.ttl,
|
|
1165
|
+
queue: intentOptions.queue,
|
|
1166
|
+
});
|
|
1167
|
+
// With `queue`, the claim is only really *ours* once the server says
|
|
1168
|
+
// so (`intent_acquired` if the target was free, `intent_granted` once
|
|
1169
|
+
// we reach the head of the FIFO line). Block here on that grant so
|
|
1170
|
+
// callers — chiefly `ablo.<model>.claim` — get a handle that already
|
|
1171
|
+
// holds the lease, never a half-claimed one racing the queue.
|
|
1172
|
+
if (intentOptions.queue) {
|
|
1173
|
+
const ws = store.getSyncWebSocket();
|
|
1174
|
+
if (ws) {
|
|
1175
|
+
try {
|
|
1176
|
+
await awaitIntentGrant(ws, claim.id, {
|
|
1177
|
+
timeoutMs: intentOptions.waitTimeoutMs,
|
|
1178
|
+
maxQueueDepth: intentOptions.maxQueueDepth,
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
catch (err) {
|
|
1182
|
+
// Gave up waiting (queue too deep, timed out, or lost) — abandon
|
|
1183
|
+
// the queued intent so we don't leave a phantom entry in the
|
|
1184
|
+
// line that would block or mislead other claimers.
|
|
1185
|
+
claim.revoke();
|
|
1186
|
+
throw err;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1127
1190
|
return wrapIntentHandle(claim);
|
|
1128
1191
|
},
|
|
1129
1192
|
list(target) {
|
|
1130
|
-
return
|
|
1193
|
+
return listModelClaims(target);
|
|
1131
1194
|
},
|
|
1132
1195
|
waitFor(target, options) {
|
|
1133
|
-
return
|
|
1196
|
+
return waitForModelUnclaimed(target, options);
|
|
1134
1197
|
},
|
|
1135
1198
|
});
|
|
1136
1199
|
// Build the typed proxy — one property per model. Done after publicIntents
|
|
1137
|
-
// exists so model
|
|
1200
|
+
// exists so model clients can expose workflow helpers such as
|
|
1138
1201
|
// `ablo.files.edit(...)` without importing protocol wiring.
|
|
1139
1202
|
const modelProxies = {};
|
|
1140
1203
|
for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
|
|
@@ -1147,13 +1210,14 @@ export function Ablo(options) {
|
|
|
1147
1210
|
getLastSyncId: () => store.getSyncWebSocket()?.getLastSyncId() ?? store.lastSyncId ?? 0,
|
|
1148
1211
|
entities: { [modelKey]: id },
|
|
1149
1212
|
}),
|
|
1213
|
+
queue: (target) => publicIntents.queueFor({ type: target.model, id: target.id }),
|
|
1150
1214
|
observe: (target) => {
|
|
1151
1215
|
// The live intent stream only tracks *open* (active) claims;
|
|
1152
1216
|
// terminal states (committed / expired / canceled) drop out of
|
|
1153
1217
|
// the list entirely — exactly the ephemeral coordination model.
|
|
1154
1218
|
// So a present entry is, by definition, `status: 'active'`.
|
|
1155
1219
|
const held = publicIntents.list({
|
|
1156
|
-
|
|
1220
|
+
model: target.model,
|
|
1157
1221
|
id: target.id,
|
|
1158
1222
|
})[0];
|
|
1159
1223
|
if (!held)
|
|
@@ -1163,7 +1227,7 @@ export function Ablo(options) {
|
|
|
1163
1227
|
id: held.id,
|
|
1164
1228
|
status: 'active',
|
|
1165
1229
|
target: {
|
|
1166
|
-
type: held.target.
|
|
1230
|
+
type: held.target.model,
|
|
1167
1231
|
id: held.target.id,
|
|
1168
1232
|
...(held.target.path ? { path: held.target.path } : {}),
|
|
1169
1233
|
...(held.target.range ? { range: held.target.range } : {}),
|
|
@@ -1176,7 +1240,7 @@ export function Ablo(options) {
|
|
|
1176
1240
|
expiresAt: held.expiresAt,
|
|
1177
1241
|
};
|
|
1178
1242
|
},
|
|
1179
|
-
waitFor: (target, waitOptions) => publicIntents.waitFor({
|
|
1243
|
+
waitFor: (target, waitOptions) => publicIntents.waitFor({ model: target.model, id: target.id }, waitOptions),
|
|
1180
1244
|
selfParticipantId: participantId,
|
|
1181
1245
|
});
|
|
1182
1246
|
}
|
|
@@ -1208,8 +1272,8 @@ export function Ablo(options) {
|
|
|
1208
1272
|
return { id: clientTxId, status: 'confirmed', lastSyncId };
|
|
1209
1273
|
},
|
|
1210
1274
|
};
|
|
1211
|
-
async function
|
|
1212
|
-
await
|
|
1275
|
+
async function retrieveModel(modelName, id, options) {
|
|
1276
|
+
await applyClaimedPolicy({ model: modelName, id }, options);
|
|
1213
1277
|
await ready();
|
|
1214
1278
|
const res = await fetchImpl(`${bootstrapHelper.baseUrl}/sync/query`, {
|
|
1215
1279
|
method: 'POST',
|
|
@@ -1218,7 +1282,7 @@ export function Ablo(options) {
|
|
|
1218
1282
|
body: JSON.stringify({
|
|
1219
1283
|
queries: [
|
|
1220
1284
|
{
|
|
1221
|
-
model:
|
|
1285
|
+
model: modelName,
|
|
1222
1286
|
where: [['id', '=', id]],
|
|
1223
1287
|
limit: 1,
|
|
1224
1288
|
},
|
|
@@ -1236,14 +1300,14 @@ export function Ablo(options) {
|
|
|
1236
1300
|
}
|
|
1237
1301
|
}
|
|
1238
1302
|
if (!res.ok) {
|
|
1239
|
-
throw translateHttpError(res.status, body || `
|
|
1303
|
+
throw translateHttpError(res.status, body || `Model retrieve failed: ${res.status} ${res.statusText}`, res.headers.get('x-request-id') ?? undefined);
|
|
1240
1304
|
}
|
|
1241
1305
|
const parsed = body;
|
|
1242
1306
|
const slot = parsed.results?.[0];
|
|
1243
1307
|
const rows = Array.isArray(slot) ? slot : [];
|
|
1244
1308
|
const data = rows[0];
|
|
1245
1309
|
if (!data) {
|
|
1246
|
-
throw new AbloValidationError(`
|
|
1310
|
+
throw new AbloValidationError(`Model row not found: ${modelName}/${id}`, { code: 'model_not_found' });
|
|
1247
1311
|
}
|
|
1248
1312
|
const stamp = typeof parsed.lastSyncId === 'number'
|
|
1249
1313
|
? parsed.lastSyncId
|
|
@@ -1251,28 +1315,27 @@ export function Ablo(options) {
|
|
|
1251
1315
|
return {
|
|
1252
1316
|
data,
|
|
1253
1317
|
stamp,
|
|
1254
|
-
|
|
1318
|
+
claims: listModelClaims({ model: modelName, id }),
|
|
1255
1319
|
};
|
|
1256
1320
|
}
|
|
1257
|
-
function
|
|
1321
|
+
function model(name) {
|
|
1258
1322
|
return {
|
|
1259
1323
|
retrieve(id, options) {
|
|
1260
|
-
return
|
|
1324
|
+
return retrieveModel(name, id, options);
|
|
1261
1325
|
},
|
|
1262
1326
|
async create(data, mutationOptions) {
|
|
1263
|
-
const id = mutationOptions?.id ??
|
|
1264
|
-
await
|
|
1327
|
+
const id = mutationOptions?.id ?? createModelId();
|
|
1328
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1265
1329
|
return commits.create({
|
|
1266
1330
|
intent: mutationOptions?.intent,
|
|
1267
1331
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1268
1332
|
readAt: mutationOptions?.readAt,
|
|
1269
1333
|
onStale: mutationOptions?.onStale,
|
|
1270
1334
|
wait: mutationOptions?.wait,
|
|
1271
|
-
timeout: mutationOptions?.timeout,
|
|
1272
1335
|
operations: [
|
|
1273
1336
|
{
|
|
1274
1337
|
action: 'create',
|
|
1275
|
-
|
|
1338
|
+
model: name,
|
|
1276
1339
|
id,
|
|
1277
1340
|
data,
|
|
1278
1341
|
},
|
|
@@ -1280,18 +1343,17 @@ export function Ablo(options) {
|
|
|
1280
1343
|
});
|
|
1281
1344
|
},
|
|
1282
1345
|
async update(id, data, mutationOptions) {
|
|
1283
|
-
await
|
|
1346
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1284
1347
|
return commits.create({
|
|
1285
1348
|
intent: mutationOptions?.intent,
|
|
1286
1349
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1287
1350
|
readAt: mutationOptions?.readAt,
|
|
1288
1351
|
onStale: mutationOptions?.onStale,
|
|
1289
1352
|
wait: mutationOptions?.wait,
|
|
1290
|
-
timeout: mutationOptions?.timeout,
|
|
1291
1353
|
operations: [
|
|
1292
1354
|
{
|
|
1293
1355
|
action: 'update',
|
|
1294
|
-
|
|
1356
|
+
model: name,
|
|
1295
1357
|
id,
|
|
1296
1358
|
data,
|
|
1297
1359
|
},
|
|
@@ -1299,18 +1361,17 @@ export function Ablo(options) {
|
|
|
1299
1361
|
});
|
|
1300
1362
|
},
|
|
1301
1363
|
async delete(id, mutationOptions) {
|
|
1302
|
-
await
|
|
1364
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1303
1365
|
return commits.create({
|
|
1304
1366
|
intent: mutationOptions?.intent,
|
|
1305
1367
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1306
1368
|
readAt: mutationOptions?.readAt,
|
|
1307
1369
|
onStale: mutationOptions?.onStale,
|
|
1308
1370
|
wait: mutationOptions?.wait,
|
|
1309
|
-
timeout: mutationOptions?.timeout,
|
|
1310
1371
|
operations: [
|
|
1311
1372
|
{
|
|
1312
1373
|
action: 'delete',
|
|
1313
|
-
|
|
1374
|
+
model: name,
|
|
1314
1375
|
id,
|
|
1315
1376
|
},
|
|
1316
1377
|
],
|
|
@@ -1387,7 +1448,7 @@ export function Ablo(options) {
|
|
|
1387
1448
|
/** Intent livestream — same socket. Stable reference. */
|
|
1388
1449
|
intents: publicIntents,
|
|
1389
1450
|
commits,
|
|
1390
|
-
|
|
1451
|
+
model,
|
|
1391
1452
|
/** Structured multiplayer participation — target-first, no
|
|
1392
1453
|
* sync-group strings in the common path. */
|
|
1393
1454
|
participants: participantManager,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Stateless API client for `Ablo({ apiKey })`.
|
|
3
3
|
*
|
|
4
4
|
* This is the hosted-API product surface: no schema, no object pool, no
|
|
5
|
-
* IndexedDB, no WebSocket. It maps the public
|
|
5
|
+
* IndexedDB, no WebSocket. It maps the public Model / Claim / Commit
|
|
6
6
|
* nouns directly to HTTP routes on sync-server.
|
|
7
7
|
*/
|
|
8
|
-
import type { AbloOptions, CommitReceipt, CommitResource, IntentCreateOptions, IntentHandle, IntentWaitOptions,
|
|
8
|
+
import type { AbloOptions, CommitReceipt, CommitResource, IntentCreateOptions, IntentHandle, IntentWaitOptions, ModelClient, ModelClaim, ModelMutationOptions, ModelReadOptions, ModelRead, ModelTarget, Turn } from './Ablo.js';
|
|
9
9
|
import type { Duration } from '../utils/duration.js';
|
|
10
10
|
export type AbloApiClientOptions = Omit<AbloOptions, 'schema'> & {
|
|
11
11
|
readonly schema?: null | undefined;
|
|
@@ -13,8 +13,8 @@ export type AbloApiClientOptions = Omit<AbloOptions, 'schema'> & {
|
|
|
13
13
|
};
|
|
14
14
|
export interface AbloApiIntents {
|
|
15
15
|
create(options: IntentCreateOptions): Promise<IntentHandle>;
|
|
16
|
-
list(target?: Partial<
|
|
17
|
-
waitFor(target: Partial<
|
|
16
|
+
list(target?: Partial<ModelTarget>): Promise<readonly ModelClaim[]>;
|
|
17
|
+
waitFor(target: Partial<ModelTarget>, options?: IntentWaitOptions): Promise<void>;
|
|
18
18
|
}
|
|
19
19
|
export type CapabilityParticipantKind = 'agent' | 'system';
|
|
20
20
|
export interface CapabilityCreateBaseOptions {
|
|
@@ -157,28 +157,28 @@ export interface AgentIntentOptions {
|
|
|
157
157
|
readonly action: string;
|
|
158
158
|
readonly field?: string;
|
|
159
159
|
readonly ttl?: Duration;
|
|
160
|
-
readonly target?: Partial<
|
|
160
|
+
readonly target?: Partial<ModelTarget>;
|
|
161
161
|
}
|
|
162
162
|
export type AgentIntentInput = string | AgentIntentOptions;
|
|
163
|
-
export interface
|
|
163
|
+
export interface AgentModelReadOptions extends ModelReadOptions {
|
|
164
164
|
}
|
|
165
|
-
export interface
|
|
165
|
+
export interface AgentModelMutationOptions extends Omit<ModelMutationOptions, 'intent'> {
|
|
166
166
|
readonly intent?: AgentIntentInput | {
|
|
167
167
|
readonly id: string;
|
|
168
168
|
} | null;
|
|
169
169
|
}
|
|
170
|
-
export interface
|
|
171
|
-
retrieve(id: string, options?:
|
|
172
|
-
create(data: Record<string, unknown>, options?:
|
|
170
|
+
export interface AgentModelClient<T = Record<string, unknown>> {
|
|
171
|
+
retrieve(id: string, options?: AgentModelReadOptions): Promise<ModelRead<T>>;
|
|
172
|
+
create(data: Record<string, unknown>, options?: AgentModelMutationOptions & {
|
|
173
173
|
readonly id?: string | null;
|
|
174
174
|
}): Promise<CommitReceipt>;
|
|
175
|
-
update(id: string, data: Record<string, unknown>, options?:
|
|
176
|
-
delete(id: string, options?:
|
|
175
|
+
update(id: string, data: Record<string, unknown>, options?: AgentModelMutationOptions): Promise<CommitReceipt>;
|
|
176
|
+
delete(id: string, options?: AgentModelMutationOptions): Promise<CommitReceipt>;
|
|
177
177
|
}
|
|
178
178
|
export interface AgentRunContext {
|
|
179
179
|
readonly task: Task;
|
|
180
180
|
readonly ablo: AbloApi;
|
|
181
|
-
|
|
181
|
+
model<T = Record<string, unknown>>(name: string): AgentModelClient<T>;
|
|
182
182
|
}
|
|
183
183
|
export interface Agent {
|
|
184
184
|
readonly id: string;
|
|
@@ -194,7 +194,7 @@ export interface AbloApi {
|
|
|
194
194
|
readonly intents: AbloApiIntents;
|
|
195
195
|
readonly commits: CommitResource;
|
|
196
196
|
agent(id: string, options: AgentOptions): Agent;
|
|
197
|
-
|
|
197
|
+
model<T = Record<string, unknown>>(name: string): ModelClient<T>;
|
|
198
198
|
beginTurn(options: TaskCreateOptions): Promise<Turn>;
|
|
199
199
|
}
|
|
200
200
|
export declare function createProtocolClient(options: AbloApiClientOptions): AbloApi;
|