@abloatai/transaction 0.43.0 → 0.45.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.
Files changed (41) hide show
  1. package/dist/ai-sdk/modelTools.d.ts +2 -4
  2. package/dist/ai-sdk/modelTools.d.ts.map +1 -1
  3. package/dist/ai-sdk/modelTools.js.map +1 -1
  4. package/dist/ai-sdk/updateTool.d.ts +2 -4
  5. package/dist/ai-sdk/updateTool.d.ts.map +1 -1
  6. package/dist/ai-sdk/updateTool.js.map +1 -1
  7. package/dist/branches.d.ts +4 -4
  8. package/dist/coordination/awaitClaimGrant.d.ts +10 -0
  9. package/dist/coordination/awaitClaimGrant.d.ts.map +1 -1
  10. package/dist/coordination/awaitClaimGrant.js +58 -45
  11. package/dist/coordination/awaitClaimGrant.js.map +1 -1
  12. package/dist/errors.d.ts +7 -4
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/errors.js +14 -6
  15. package/dist/errors.js.map +1 -1
  16. package/dist/resources/httpResources.d.ts +4 -4
  17. package/dist/resources/httpResources.d.ts.map +1 -1
  18. package/dist/resources/modelOperations.d.ts +113 -21
  19. package/dist/resources/modelOperations.d.ts.map +1 -1
  20. package/dist/resources/modelOperations.js +43 -1
  21. package/dist/resources/modelOperations.js.map +1 -1
  22. package/dist/transport/httpTransport.d.ts.map +1 -1
  23. package/dist/transport/httpTransport.js +49 -15
  24. package/dist/transport/httpTransport.js.map +1 -1
  25. package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
  26. package/dist/transport/wsFrameHandlers.js +12 -0
  27. package/dist/transport/wsFrameHandlers.js.map +1 -1
  28. package/dist/wire/commit.d.ts +6 -0
  29. package/dist/wire/commit.d.ts.map +1 -1
  30. package/dist/wire/commit.js +2 -0
  31. package/dist/wire/commit.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/ai-sdk/modelTools.ts +2 -1
  34. package/src/ai-sdk/updateTool.ts +2 -1
  35. package/src/coordination/awaitClaimGrant.ts +86 -74
  36. package/src/errors.ts +17 -5
  37. package/src/resources/httpResources.ts +5 -1
  38. package/src/resources/modelOperations.ts +169 -19
  39. package/src/transport/httpTransport.ts +73 -19
  40. package/src/transport/wsFrameHandlers.ts +14 -0
  41. package/src/wire/commit.ts +2 -0
@@ -8,6 +8,7 @@
8
8
 
9
9
  import {
10
10
  AbloClaimedError,
11
+ AbloError,
11
12
  AbloAuthenticationError,
12
13
  AbloConnectionError,
13
14
  AbloIdempotencyError,
@@ -113,10 +114,19 @@ import type {
113
114
  ClaimLookupParams,
114
115
  ClaimOptions,
115
116
  ClaimParams,
117
+ ClaimSkipParams,
116
118
  ClaimReorderParams,
117
119
  ModelTrackParams,
118
120
  ModelTrackResult,
119
121
  ServerReadOptions,
122
+ ResolvedClaimContentionOptions,
123
+ ClaimQueueView,
124
+ } from '../resources/modelOperations.js';
125
+ import {
126
+ claimAttemptFailure,
127
+ claimQueueView,
128
+ emitClaimStatus,
129
+ resolveClaimContentionOptions,
120
130
  } from '../resources/modelOperations.js';
121
131
  import type { Duration } from '../utils/duration.js';
122
132
  import type { TrackDependency } from '../coordination/schema.js';
@@ -1148,7 +1158,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1148
1158
  async function awaitGrantOverHttp(
1149
1159
  targetLabel: string,
1150
1160
  queued: ClaimQueuedResponse,
1151
- options: { maxQueueDepth?: number; waitTimeoutMs?: number; signal?: AbortSignal }
1161
+ options: ResolvedClaimContentionOptions,
1152
1162
  ): Promise<{ id: string; fenceToken?: number }> {
1153
1163
  // The queued reply is a claim resource in its waiting state, so the
1154
1164
  // handle is its `id` — same rule as the 201 and the poll.
@@ -1161,17 +1171,24 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1161
1171
  throw error;
1162
1172
  };
1163
1173
 
1164
- if (options.maxQueueDepth !== undefined && queued.position >= options.maxQueueDepth) {
1174
+ emitClaimStatus(options.onStatus, {
1175
+ type: 'queued',
1176
+ claimId,
1177
+ position: queued.position,
1178
+ ahead: queued.position + 1,
1179
+ });
1180
+
1181
+ if (options.maxDepth !== undefined && queued.position >= options.maxDepth) {
1165
1182
  return rejectAndLeave(
1166
1183
  new AbloClaimedError(
1167
- `Claim queue for ${targetLabel} is ${queued.position} deep (max ${options.maxQueueDepth}).`,
1184
+ `Claim queue for ${targetLabel} is ${queued.position} deep (max ${options.maxDepth}).`,
1168
1185
  { code: 'queue_too_deep' }
1169
1186
  )
1170
1187
  );
1171
1188
  }
1172
1189
 
1173
1190
  const deadline =
1174
- options.waitTimeoutMs !== undefined ? Date.now() + options.waitTimeoutMs : undefined;
1191
+ options.timeoutMs !== undefined ? Date.now() + options.timeoutMs : undefined;
1175
1192
  let delay = GRANT_POLL_FIRST_MS;
1176
1193
  for (;;) {
1177
1194
  if (signal?.aborted) {
@@ -1185,7 +1202,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1185
1202
  if (deadline !== undefined && Date.now() >= deadline) {
1186
1203
  return rejectAndLeave(
1187
1204
  new AbloClaimedError(
1188
- `Timed out after ${options.waitTimeoutMs}ms waiting for the queue grant on ${targetLabel}.`,
1205
+ `Timed out after ${options.timeoutMs}ms waiting for the queue grant on ${targetLabel}.`,
1189
1206
  { code: 'grant_timeout' }
1190
1207
  )
1191
1208
  );
@@ -1218,6 +1235,11 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1218
1235
  })
1219
1236
  );
1220
1237
  }
1238
+ emitClaimStatus(options.onStatus, {
1239
+ type: 'granted',
1240
+ claimId,
1241
+ waited: true,
1242
+ });
1221
1243
  return state.fenceToken !== undefined
1222
1244
  ? { id: claimId, fenceToken: state.fenceToken }
1223
1245
  : { id: claimId };
@@ -1492,6 +1514,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1492
1514
  const acquireClaim = async (
1493
1515
  params: ClaimParams<Fields>
1494
1516
  ): Promise<{ id: string; fenceToken?: number }> => {
1517
+ const contention = resolveClaimContentionOptions(params);
1495
1518
  // The row is named by the URL, so `target` carries only the narrowing a
1496
1519
  // claim adds below it. Sending it is what makes a field-scoped claim
1497
1520
  // actually field-scoped: the server's conflict rule reads `path`,
@@ -1512,13 +1535,25 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1512
1535
  ...(Object.keys(narrowing).length > 0 ? { target: narrowing } : {}),
1513
1536
  // `queue` (default true) → queue behind the holder; false → fail-fast
1514
1537
  // with AbloClaimedError (work-distribution dedup).
1515
- queue: params.queue ?? true,
1538
+ queue: contention.wait,
1516
1539
  };
1517
- const body = await requestJson(
1518
- claimPath(params.id),
1519
- { method: 'POST', body: JSON.stringify(request) },
1520
- claimAcquireResponseSchema
1521
- );
1540
+ let body: z.infer<typeof claimAcquireResponseSchema>;
1541
+ try {
1542
+ body = await requestJson(
1543
+ claimPath(params.id),
1544
+ { method: 'POST', body: JSON.stringify(request) },
1545
+ claimAcquireResponseSchema
1546
+ );
1547
+ } catch (error) {
1548
+ const normalized = error instanceof AbloError
1549
+ ? error
1550
+ : new AbloConnectionError(String(error));
1551
+ emitClaimStatus(
1552
+ contention.onStatus,
1553
+ claimAttemptFailure(contention.wait, normalized),
1554
+ );
1555
+ throw error;
1556
+ }
1522
1557
  // One resource, two states, discriminated by `status`. The queued arm
1523
1558
  // WAITS, exactly as the socket client does: `claim({ id })` means
1524
1559
  // "serialize me behind the holder" on every transport, and the grant
@@ -1527,8 +1562,28 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1527
1562
  // why it no longer surfaces as one here. The `claims` namespace remains
1528
1563
  // the manual ticket surface.)
1529
1564
  if (body.status === 'queued') {
1530
- return awaitGrantOverHttp(`${name}/${params.id}`, body, params);
1565
+ try {
1566
+ return await awaitGrantOverHttp(
1567
+ `${name}/${params.id}`,
1568
+ body,
1569
+ contention,
1570
+ );
1571
+ } catch (error) {
1572
+ const normalized = error instanceof AbloError
1573
+ ? error
1574
+ : new AbloConnectionError(String(error));
1575
+ emitClaimStatus(
1576
+ contention.onStatus,
1577
+ claimAttemptFailure(contention.wait, normalized),
1578
+ );
1579
+ throw error;
1580
+ }
1531
1581
  }
1582
+ emitClaimStatus(contention.onStatus, {
1583
+ type: 'granted',
1584
+ claimId: body.id,
1585
+ waited: false,
1586
+ });
1532
1587
  // The lease's own fields are mirrored at the top level, the same place
1533
1588
  // the poll puts them — one reader for both answers.
1534
1589
  return body.fenceToken !== undefined
@@ -1566,7 +1621,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1566
1621
  };
1567
1622
 
1568
1623
  function claimImpl(
1569
- params: ClaimParams<Fields> & { queue: false }
1624
+ params: ClaimSkipParams<Fields>
1570
1625
  ): Promise<HeldClaim<T> | null>;
1571
1626
  function claimImpl(params: ClaimParams<Fields>): Promise<HeldClaim<T>>;
1572
1627
  async function claimImpl(
@@ -1582,7 +1637,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1582
1637
  // and the write-site claim path calls `acquireClaim` directly, so a
1583
1638
  // write that could not claim still fails loudly.
1584
1639
  if (
1585
- params.queue === false &&
1640
+ !resolveClaimContentionOptions(params).wait &&
1586
1641
  error instanceof AbloClaimedError &&
1587
1642
  (error.code === 'entity_claimed' || error.code === 'claim_conflict')
1588
1643
  ) {
@@ -1687,14 +1742,13 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
1687
1742
  },
1688
1743
  queue: async (
1689
1744
  params: ClaimLookupParams<T>
1690
- ): Promise<{ readonly object: 'list'; readonly data: readonly Claim[] }> => {
1745
+ ): Promise<ClaimQueueView> => {
1691
1746
  const res = await claimsForEntity(params);
1692
- return {
1693
- object: 'list',
1694
- data: res.data
1747
+ return claimQueueView(
1748
+ res.data
1695
1749
  .filter((row) => row.status === 'queued')
1696
1750
  .map(claimFromModelClaim),
1697
- };
1751
+ );
1698
1752
  },
1699
1753
  reorder: async (params: ClaimReorderParams<T>): Promise<void> => {
1700
1754
  await requestRaw(`${claimPath(params.id)}/reorder`, {
@@ -223,14 +223,18 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
223
223
  // such as validation issues, survive being wrapped in an Error.
224
224
  let errorMessage: string;
225
225
  let errorCode: string | undefined;
226
+ let requestId: string | undefined;
226
227
  let requiredCapability: RequiredCapability | undefined;
228
+ let details: Readonly<Record<string, unknown>> | undefined;
227
229
  if (typeof error === 'string') {
228
230
  errorMessage = error;
229
231
  } else if (error != null && typeof error === 'object') {
230
232
  const obj = error as {
231
233
  code?: unknown;
232
234
  message?: unknown;
235
+ request_id?: unknown;
233
236
  requiredCapability?: unknown;
237
+ details?: unknown;
234
238
  };
235
239
  if (typeof obj.code === 'string') errorCode = obj.code;
236
240
  if (typeof obj.message === 'string') {
@@ -249,6 +253,14 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
249
253
  ) {
250
254
  requiredCapability = obj.requiredCapability as RequiredCapability;
251
255
  }
256
+ requestId =
257
+ typeof obj.request_id === 'string' ? obj.request_id : undefined;
258
+ details =
259
+ obj.details != null &&
260
+ typeof obj.details === 'object' &&
261
+ !Array.isArray(obj.details)
262
+ ? (obj.details as Readonly<Record<string, unknown>>)
263
+ : undefined;
252
264
  } else {
253
265
  errorMessage = 'mutation failed on server';
254
266
  }
@@ -298,7 +310,9 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
298
310
  pending.reject(
299
311
  errorFromWire(errorMessage, {
300
312
  code: errorCode,
313
+ requestId,
301
314
  requiredCapability,
315
+ details,
302
316
  }),
303
317
  );
304
318
  }
@@ -157,7 +157,9 @@ export const rejectedCommitReceiptSchema = z.object({
157
157
  code: errorCodeSchema,
158
158
  message: z.string(),
159
159
  field: z.string().optional(),
160
+ request_id: z.string().optional(),
160
161
  requiredCapability: requiredCapabilityWireSchema.optional(),
162
+ details: z.record(z.string(), z.unknown()).optional(),
161
163
  }),
162
164
  });
163
165
  export type RejectedCommitReceiptWire = z.infer<typeof rejectedCommitReceiptSchema>;