@abloatai/ablo 0.5.1 → 0.7.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 +61 -0
- package/README.md +248 -124
- package/dist/BaseSyncedStore.d.ts +3 -3
- package/dist/BaseSyncedStore.js +3 -3
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.js +1 -1
- package/dist/client/Ablo.d.ts +91 -93
- package/dist/client/Ablo.js +122 -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 +116 -90
- package/dist/client/createModelProxy.js +128 -128
- package/dist/client/index.d.ts +6 -7
- package/dist/client/index.js +4 -5
- package/dist/client/validateAbloOptions.js +5 -5
- package/dist/coordination/index.d.ts +6 -0
- package/dist/coordination/index.js +6 -0
- package/dist/coordination/schema.d.ts +329 -0
- package/dist/coordination/schema.js +209 -0
- package/dist/core/QueryView.d.ts +4 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +7 -0
- package/dist/core/query-utils.d.ts +7 -10
- package/dist/core/query-utils.js +2 -3
- package/dist/errorCodes.d.ts +264 -0
- package/dist/errorCodes.js +251 -0
- package/dist/errors.d.ts +59 -14
- package/dist/errors.js +73 -12
- package/dist/index.d.ts +11 -9
- package/dist/index.js +8 -12
- 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/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/types.d.ts +31 -0
- package/dist/policy/types.js +15 -0
- package/dist/query/types.d.ts +1 -1
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +14 -6
- 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/ddl.d.ts +62 -0
- package/dist/schema/ddl.js +317 -0
- package/dist/schema/diff.d.ts +167 -0
- package/dist/schema/diff.js +280 -0
- package/dist/schema/field.d.ts +16 -19
- package/dist/schema/field.js +30 -17
- package/dist/schema/generate.d.ts +19 -0
- package/dist/schema/generate.js +87 -0
- package/dist/schema/index.d.ts +9 -3
- package/dist/schema/index.js +14 -2
- package/dist/schema/model.d.ts +87 -25
- package/dist/schema/model.js +33 -3
- package/dist/schema/relation.d.ts +17 -0
- package/dist/schema/roles.d.ts +148 -0
- package/dist/schema/roles.js +149 -0
- package/dist/schema/schema.d.ts +10 -69
- package/dist/schema/schema.js +58 -24
- package/dist/schema/select.d.ts +25 -0
- package/dist/schema/select.js +55 -0
- package/dist/schema/serialize.d.ts +96 -0
- package/dist/schema/serialize.js +231 -0
- package/dist/schema/sugar.d.ts +20 -3
- package/dist/schema/sugar.js +5 -1
- package/dist/schema/tenancy.d.ts +66 -0
- package/dist/schema/tenancy.js +58 -0
- package/dist/sync/HydrationCoordinator.d.ts +2 -0
- package/dist/sync/HydrationCoordinator.js +23 -17
- 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.d.ts +2 -1
- package/dist/sync/createIntentStream.js +89 -5
- package/dist/sync/createPresenceStream.js +1 -1
- package/dist/sync/participants.d.ts +2 -2
- package/dist/sync/participants.js +9 -18
- package/dist/types/global.d.ts +43 -52
- package/dist/types/global.js +16 -18
- package/dist/types/streams.d.ts +90 -42
- package/docs/api-keys.md +44 -0
- package/docs/api.md +72 -173
- package/docs/audit.md +5 -5
- package/docs/cli.md +212 -0
- package/docs/client-behavior.md +42 -43
- package/docs/coordination.md +343 -0
- package/docs/data-sources.md +16 -16
- package/docs/examples/agent-human.md +30 -32
- package/docs/examples/ai-sdk-tool.md +32 -33
- package/docs/examples/existing-python-backend.md +38 -36
- package/docs/examples/nextjs.md +24 -25
- package/docs/examples/scoped-agent.md +78 -0
- package/docs/examples/server-agent.md +20 -61
- package/docs/guarantees.md +34 -56
- package/docs/identity.md +529 -0
- package/docs/index.md +18 -24
- package/docs/integration-guide.md +130 -144
- package/docs/interaction-model.md +32 -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 +74 -24
- package/docs/roadmap.md +17 -7
- package/llms.txt +34 -39
- package/package.json +8 -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,15 @@ 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 }),
|
|
1214
|
+
reorder: (target, order) => publicIntents.reorder({ type: target.model, id: target.id }, order),
|
|
1150
1215
|
observe: (target) => {
|
|
1151
1216
|
// The live intent stream only tracks *open* (active) claims;
|
|
1152
1217
|
// terminal states (committed / expired / canceled) drop out of
|
|
1153
1218
|
// the list entirely — exactly the ephemeral coordination model.
|
|
1154
1219
|
// So a present entry is, by definition, `status: 'active'`.
|
|
1155
1220
|
const held = publicIntents.list({
|
|
1156
|
-
|
|
1221
|
+
model: target.model,
|
|
1157
1222
|
id: target.id,
|
|
1158
1223
|
})[0];
|
|
1159
1224
|
if (!held)
|
|
@@ -1163,7 +1228,7 @@ export function Ablo(options) {
|
|
|
1163
1228
|
id: held.id,
|
|
1164
1229
|
status: 'active',
|
|
1165
1230
|
target: {
|
|
1166
|
-
type: held.target.
|
|
1231
|
+
type: held.target.model,
|
|
1167
1232
|
id: held.target.id,
|
|
1168
1233
|
...(held.target.path ? { path: held.target.path } : {}),
|
|
1169
1234
|
...(held.target.range ? { range: held.target.range } : {}),
|
|
@@ -1176,7 +1241,7 @@ export function Ablo(options) {
|
|
|
1176
1241
|
expiresAt: held.expiresAt,
|
|
1177
1242
|
};
|
|
1178
1243
|
},
|
|
1179
|
-
waitFor: (target, waitOptions) => publicIntents.waitFor({
|
|
1244
|
+
waitFor: (target, waitOptions) => publicIntents.waitFor({ model: target.model, id: target.id }, waitOptions),
|
|
1180
1245
|
selfParticipantId: participantId,
|
|
1181
1246
|
});
|
|
1182
1247
|
}
|
|
@@ -1208,8 +1273,8 @@ export function Ablo(options) {
|
|
|
1208
1273
|
return { id: clientTxId, status: 'confirmed', lastSyncId };
|
|
1209
1274
|
},
|
|
1210
1275
|
};
|
|
1211
|
-
async function
|
|
1212
|
-
await
|
|
1276
|
+
async function retrieveModel(modelName, id, options) {
|
|
1277
|
+
await applyClaimedPolicy({ model: modelName, id }, options);
|
|
1213
1278
|
await ready();
|
|
1214
1279
|
const res = await fetchImpl(`${bootstrapHelper.baseUrl}/sync/query`, {
|
|
1215
1280
|
method: 'POST',
|
|
@@ -1218,7 +1283,7 @@ export function Ablo(options) {
|
|
|
1218
1283
|
body: JSON.stringify({
|
|
1219
1284
|
queries: [
|
|
1220
1285
|
{
|
|
1221
|
-
model:
|
|
1286
|
+
model: modelName,
|
|
1222
1287
|
where: [['id', '=', id]],
|
|
1223
1288
|
limit: 1,
|
|
1224
1289
|
},
|
|
@@ -1236,14 +1301,14 @@ export function Ablo(options) {
|
|
|
1236
1301
|
}
|
|
1237
1302
|
}
|
|
1238
1303
|
if (!res.ok) {
|
|
1239
|
-
throw translateHttpError(res.status, body || `
|
|
1304
|
+
throw translateHttpError(res.status, body || `Model retrieve failed: ${res.status} ${res.statusText}`, res.headers.get('x-request-id') ?? undefined);
|
|
1240
1305
|
}
|
|
1241
1306
|
const parsed = body;
|
|
1242
1307
|
const slot = parsed.results?.[0];
|
|
1243
1308
|
const rows = Array.isArray(slot) ? slot : [];
|
|
1244
1309
|
const data = rows[0];
|
|
1245
1310
|
if (!data) {
|
|
1246
|
-
throw new AbloValidationError(`
|
|
1311
|
+
throw new AbloValidationError(`Model row not found: ${modelName}/${id}`, { code: 'model_not_found' });
|
|
1247
1312
|
}
|
|
1248
1313
|
const stamp = typeof parsed.lastSyncId === 'number'
|
|
1249
1314
|
? parsed.lastSyncId
|
|
@@ -1251,28 +1316,27 @@ export function Ablo(options) {
|
|
|
1251
1316
|
return {
|
|
1252
1317
|
data,
|
|
1253
1318
|
stamp,
|
|
1254
|
-
|
|
1319
|
+
claims: listModelClaims({ model: modelName, id }),
|
|
1255
1320
|
};
|
|
1256
1321
|
}
|
|
1257
|
-
function
|
|
1322
|
+
function model(name) {
|
|
1258
1323
|
return {
|
|
1259
1324
|
retrieve(id, options) {
|
|
1260
|
-
return
|
|
1325
|
+
return retrieveModel(name, id, options);
|
|
1261
1326
|
},
|
|
1262
1327
|
async create(data, mutationOptions) {
|
|
1263
|
-
const id = mutationOptions?.id ??
|
|
1264
|
-
await
|
|
1328
|
+
const id = mutationOptions?.id ?? createModelId();
|
|
1329
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1265
1330
|
return commits.create({
|
|
1266
1331
|
intent: mutationOptions?.intent,
|
|
1267
1332
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1268
1333
|
readAt: mutationOptions?.readAt,
|
|
1269
1334
|
onStale: mutationOptions?.onStale,
|
|
1270
1335
|
wait: mutationOptions?.wait,
|
|
1271
|
-
timeout: mutationOptions?.timeout,
|
|
1272
1336
|
operations: [
|
|
1273
1337
|
{
|
|
1274
1338
|
action: 'create',
|
|
1275
|
-
|
|
1339
|
+
model: name,
|
|
1276
1340
|
id,
|
|
1277
1341
|
data,
|
|
1278
1342
|
},
|
|
@@ -1280,18 +1344,17 @@ export function Ablo(options) {
|
|
|
1280
1344
|
});
|
|
1281
1345
|
},
|
|
1282
1346
|
async update(id, data, mutationOptions) {
|
|
1283
|
-
await
|
|
1347
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1284
1348
|
return commits.create({
|
|
1285
1349
|
intent: mutationOptions?.intent,
|
|
1286
1350
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1287
1351
|
readAt: mutationOptions?.readAt,
|
|
1288
1352
|
onStale: mutationOptions?.onStale,
|
|
1289
1353
|
wait: mutationOptions?.wait,
|
|
1290
|
-
timeout: mutationOptions?.timeout,
|
|
1291
1354
|
operations: [
|
|
1292
1355
|
{
|
|
1293
1356
|
action: 'update',
|
|
1294
|
-
|
|
1357
|
+
model: name,
|
|
1295
1358
|
id,
|
|
1296
1359
|
data,
|
|
1297
1360
|
},
|
|
@@ -1299,18 +1362,17 @@ export function Ablo(options) {
|
|
|
1299
1362
|
});
|
|
1300
1363
|
},
|
|
1301
1364
|
async delete(id, mutationOptions) {
|
|
1302
|
-
await
|
|
1365
|
+
await applyClaimedPolicy({ model: name, id }, mutationOptions);
|
|
1303
1366
|
return commits.create({
|
|
1304
1367
|
intent: mutationOptions?.intent,
|
|
1305
1368
|
idempotencyKey: mutationOptions?.idempotencyKey,
|
|
1306
1369
|
readAt: mutationOptions?.readAt,
|
|
1307
1370
|
onStale: mutationOptions?.onStale,
|
|
1308
1371
|
wait: mutationOptions?.wait,
|
|
1309
|
-
timeout: mutationOptions?.timeout,
|
|
1310
1372
|
operations: [
|
|
1311
1373
|
{
|
|
1312
1374
|
action: 'delete',
|
|
1313
|
-
|
|
1375
|
+
model: name,
|
|
1314
1376
|
id,
|
|
1315
1377
|
},
|
|
1316
1378
|
],
|
|
@@ -1387,7 +1449,7 @@ export function Ablo(options) {
|
|
|
1387
1449
|
/** Intent livestream — same socket. Stable reference. */
|
|
1388
1450
|
intents: publicIntents,
|
|
1389
1451
|
commits,
|
|
1390
|
-
|
|
1452
|
+
model,
|
|
1391
1453
|
/** Structured multiplayer participation — target-first, no
|
|
1392
1454
|
* sync-group strings in the common path. */
|
|
1393
1455
|
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;
|