@abloatai/transaction 0.60.0 → 0.61.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 (88) hide show
  1. package/dist/claims/admission.d.ts +4 -0
  2. package/dist/claims/admission.d.ts.map +1 -0
  3. package/dist/claims/admission.js +32 -0
  4. package/dist/claims/admission.js.map +1 -0
  5. package/dist/claims/awaitGrant.d.ts.map +1 -1
  6. package/dist/claims/awaitGrant.js +4 -38
  7. package/dist/claims/awaitGrant.js.map +1 -1
  8. package/dist/claims/conflict.d.ts +55 -0
  9. package/dist/claims/conflict.d.ts.map +1 -0
  10. package/dist/claims/conflict.js +71 -0
  11. package/dist/claims/conflict.js.map +1 -0
  12. package/dist/claims/contract.d.ts +12 -0
  13. package/dist/claims/contract.d.ts.map +1 -1
  14. package/dist/claims/contract.js +2 -0
  15. package/dist/claims/contract.js.map +1 -1
  16. package/dist/claims/index.d.ts +2 -0
  17. package/dist/claims/index.d.ts.map +1 -1
  18. package/dist/claims/index.js +1 -0
  19. package/dist/claims/index.js.map +1 -1
  20. package/dist/coordination/index.d.ts +2 -2
  21. package/dist/coordination/index.d.ts.map +1 -1
  22. package/dist/coordination/index.js +1 -1
  23. package/dist/coordination/index.js.map +1 -1
  24. package/dist/coordination/schema.d.ts +7 -0
  25. package/dist/coordination/schema.d.ts.map +1 -1
  26. package/dist/coordination/schema.js +7 -0
  27. package/dist/coordination/schema.js.map +1 -1
  28. package/dist/errorCodes.d.ts.map +1 -1
  29. package/dist/errorCodes.js +12 -1
  30. package/dist/errorCodes.js.map +1 -1
  31. package/dist/errors.d.ts +6 -0
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +101 -5
  34. package/dist/errors.js.map +1 -1
  35. package/dist/transport/http/claimWait.d.ts +9 -0
  36. package/dist/transport/http/claimWait.d.ts.map +1 -0
  37. package/dist/transport/http/claimWait.js +74 -0
  38. package/dist/transport/http/claimWait.js.map +1 -0
  39. package/dist/transport/http/client.d.ts.map +1 -1
  40. package/dist/transport/http/client.js +3 -4
  41. package/dist/transport/http/client.js.map +1 -1
  42. package/dist/transport/http/contract.d.ts +57 -0
  43. package/dist/transport/http/contract.d.ts.map +1 -0
  44. package/dist/transport/http/contract.js +3 -0
  45. package/dist/transport/http/contract.js.map +1 -0
  46. package/dist/transport/http/index.d.ts +1 -0
  47. package/dist/transport/http/index.d.ts.map +1 -1
  48. package/dist/transport/http/index.js +1 -0
  49. package/dist/transport/http/index.js.map +1 -1
  50. package/dist/transport/http/transport.d.ts +1 -78
  51. package/dist/transport/http/transport.d.ts.map +1 -1
  52. package/dist/transport/http/transport.js +7 -83
  53. package/dist/transport/http/transport.js.map +1 -1
  54. package/dist/transport/websocket/frameHandlers.d.ts.map +1 -1
  55. package/dist/transport/websocket/frameHandlers.js +3 -0
  56. package/dist/transport/websocket/frameHandlers.js.map +1 -1
  57. package/dist/transport/websocket/session.d.ts.map +1 -1
  58. package/dist/transport/websocket/session.js +69 -36
  59. package/dist/transport/websocket/session.js.map +1 -1
  60. package/dist/transport/websocket/sessionContract.d.ts +1 -1
  61. package/dist/transport/websocket/sessionContract.d.ts.map +1 -1
  62. package/dist/transport/websocket/transport.d.ts +2 -1
  63. package/dist/transport/websocket/transport.d.ts.map +1 -1
  64. package/dist/transport/websocket/transport.js.map +1 -1
  65. package/dist/wire/inboundFrames.d.ts +7 -0
  66. package/dist/wire/inboundFrames.d.ts.map +1 -1
  67. package/dist/wire/inboundFrames.js +5 -1
  68. package/dist/wire/inboundFrames.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/claims/admission.ts +49 -0
  71. package/src/claims/awaitGrant.ts +3 -52
  72. package/src/claims/conflict.ts +103 -0
  73. package/src/claims/contract.ts +2 -0
  74. package/src/claims/index.ts +11 -0
  75. package/src/coordination/index.ts +2 -0
  76. package/src/coordination/schema.ts +11 -0
  77. package/src/errorCodes.ts +18 -1
  78. package/src/errors.ts +116 -4
  79. package/src/transport/http/claimWait.ts +108 -0
  80. package/src/transport/http/client.ts +6 -5
  81. package/src/transport/http/contract.ts +71 -0
  82. package/src/transport/http/index.ts +1 -0
  83. package/src/transport/http/transport.ts +11 -204
  84. package/src/transport/websocket/frameHandlers.ts +7 -0
  85. package/src/transport/websocket/session.ts +77 -43
  86. package/src/transport/websocket/sessionContract.ts +1 -1
  87. package/src/transport/websocket/transport.ts +2 -0
  88. package/src/wire/inboundFrames.ts +5 -0
package/src/errors.ts CHANGED
@@ -23,12 +23,24 @@ import { ABLO_DOCS_BASE_URL } from './auth/hostedEndpoints.js';
23
23
  import { errorCodeSpec, classifyRecovery } from './errorCodes.js';
24
24
  import {
25
25
  wireClaimSummarySchema,
26
+ wireParticipantKindSchema,
27
+ modelTargetSchema,
26
28
  descriptionFromMeta,
27
29
  type WireClaimSummary,
28
30
  type ModelClaim,
29
31
  type ModelTarget,
30
32
  type ParticipantKind,
31
33
  } from './coordination/schema.js';
34
+ import {
35
+ claimConflictContextSchema,
36
+ normalizeClaimConflict,
37
+ type ClaimConflictContext,
38
+ } from './claims/conflict.js';
39
+
40
+ export type {
41
+ ClaimConflictContext,
42
+ ClaimConflictCounterparty,
43
+ } from './claims/conflict.js';
32
44
 
33
45
  export type { ErrorCode, WireErrorCode, ErrorCategory, ErrorCodeSpec, RecoveryClass } from './errorCodes.js';
34
46
  export {
@@ -368,6 +380,44 @@ export interface ClaimContext {
368
380
 
369
381
  export type ClaimErrorClaim = WireClaimSummary | ClaimContext;
370
382
 
383
+ function conflictFromClaims(
384
+ claims: readonly ClaimErrorClaim[] | undefined,
385
+ ): ClaimConflictContext | undefined {
386
+ const claim = claims?.[0];
387
+ if (!claim) return undefined;
388
+ const summary = wireClaimSummarySchema.safeParse(claim);
389
+ if (summary.success) {
390
+ const actor = 'actor' in claim && typeof claim.actor === 'string'
391
+ ? claim.actor
392
+ : undefined;
393
+ return normalizeClaimConflict({
394
+ heldByClaim: summary.data,
395
+ ...(actor !== undefined ? { heldBy: actor } : {}),
396
+ });
397
+ }
398
+ const context = claim as ClaimContext;
399
+ return normalizeClaimConflict({
400
+ ...(context.actor !== undefined ? { heldBy: context.actor } : {}),
401
+ ...(context.participantKind !== undefined
402
+ ? { heldByKind: context.participantKind }
403
+ : {}),
404
+ ...(context.claimId !== undefined ? { heldByClaimId: context.claimId } : {}),
405
+ ...(context.description !== undefined ? { description: context.description } : {}),
406
+ ...(context.expiresAt !== undefined ? { expiresAt: context.expiresAt } : {}),
407
+ ...(context.target !== undefined
408
+ ? { target: context.target }
409
+ : context.entityType !== undefined || context.entityId !== undefined
410
+ ? {
411
+ target: {
412
+ ...(context.entityType !== undefined ? { model: context.entityType } : {}),
413
+ ...(context.entityId !== undefined ? { id: context.entityId } : {}),
414
+ ...(context.field !== undefined ? { field: context.field } : {}),
415
+ },
416
+ }
417
+ : {}),
418
+ });
419
+ }
420
+
371
421
  function claimDescription(claim: ClaimErrorClaim | undefined): string | undefined {
372
422
  if (!claim) return undefined;
373
423
  if ('description' in claim && typeof claim.description === 'string') {
@@ -434,6 +484,7 @@ export function formatClaimedErrorMessage(args: {
434
484
  export class AbloClaimedError extends AbloError {
435
485
  override readonly type = 'AbloClaimedError' as const;
436
486
  readonly claims?: readonly ClaimErrorClaim[];
487
+ readonly conflict?: ClaimConflictContext;
437
488
 
438
489
  constructor(
439
490
  message: string,
@@ -443,10 +494,22 @@ export class AbloClaimedError extends AbloError {
443
494
  requestId?: string;
444
495
  cause?: unknown;
445
496
  claims?: readonly ClaimErrorClaim[];
497
+ conflict?: ClaimConflictContext;
498
+ details?: Readonly<Record<string, unknown>>;
446
499
  },
447
500
  ) {
448
- super(message, options);
501
+ const conflict = options?.conflict ?? conflictFromClaims(options?.claims);
502
+ const details = {
503
+ ...(options?.details ?? {}),
504
+ ...(options?.claims !== undefined ? { claims: options.claims } : {}),
505
+ ...(conflict !== undefined ? { conflict } : {}),
506
+ };
507
+ super(message, {
508
+ ...options,
509
+ ...(Object.keys(details).length > 0 ? { details } : {}),
510
+ });
449
511
  if (options?.claims !== undefined) this.claims = options.claims;
512
+ if (conflict !== undefined) this.conflict = conflict;
450
513
  }
451
514
  }
452
515
 
@@ -681,8 +744,13 @@ const NestedErrorShapeSchema = z
681
744
  field: OptionalWireStringSchema,
682
745
  requiredCapability: requiredCapabilityWireSchema.optional().catch(undefined),
683
746
  heldBy: OptionalWireStringSchema,
747
+ heldByKind: wireParticipantKindSchema.optional().catch(undefined),
748
+ heldByClaimId: OptionalWireStringSchema,
749
+ heldByExpiresAt: z.number().optional().catch(undefined),
684
750
  heldByClaim: wireClaimSummarySchema.optional().catch(undefined),
685
751
  claims: z.array(wireClaimSummarySchema).optional().catch(undefined),
752
+ conflict: claimConflictContextSchema.optional().catch(undefined),
753
+ target: modelTargetSchema.unwrap().partial().optional().catch(undefined),
686
754
  })
687
755
  .loose();
688
756
 
@@ -706,8 +774,13 @@ const ErrorBodyShapeSchema = z
706
774
  message: OptionalWireStringSchema,
707
775
  requiredCapability: requiredCapabilityWireSchema.optional().catch(undefined),
708
776
  heldBy: OptionalWireStringSchema,
777
+ heldByKind: wireParticipantKindSchema.optional().catch(undefined),
778
+ heldByClaimId: OptionalWireStringSchema,
779
+ heldByExpiresAt: z.number().optional().catch(undefined),
709
780
  heldByClaim: wireClaimSummarySchema.optional().catch(undefined),
710
781
  claims: z.array(wireClaimSummarySchema).optional().catch(undefined),
782
+ conflict: claimConflictContextSchema.optional().catch(undefined),
783
+ target: modelTargetSchema.unwrap().partial().optional().catch(undefined),
711
784
  })
712
785
  .loose();
713
786
 
@@ -763,11 +836,12 @@ export function errorFromWire(
763
836
  requestId?: string;
764
837
  requiredCapability?: RequiredCapability;
765
838
  claims?: readonly ClaimErrorClaim[];
839
+ conflict?: ClaimConflictContext;
766
840
  /** Everything the producer attached beyond the envelope's own fields. */
767
841
  details?: Readonly<Record<string, unknown>>;
768
842
  } = {},
769
843
  ): AbloError {
770
- const { code, requestId, requiredCapability, claims, details } = opts;
844
+ const { code, requestId, requiredCapability, claims, conflict, details } = opts;
771
845
  // Effective status: an explicit HTTP status wins; otherwise fall back to
772
846
  // the code's canonical status from the registry (undefined for unknown /
773
847
  // forward-compat codes, which then map to the base AbloError).
@@ -802,9 +876,16 @@ export function errorFromWire(
802
876
  if (
803
877
  code === 'claim_conflict' ||
804
878
  code === 'entity_claimed' ||
805
- code === 'claim_lost'
879
+ code === 'claim_lost' ||
880
+ code === 'fence_token_stale' ||
881
+ code === 'model_claimed' ||
882
+ code === 'model_claimed_timeout' ||
883
+ code === 'claim_lease_unavailable' ||
884
+ code === 'claim_wait_aborted' ||
885
+ code === 'grant_timeout' ||
886
+ code === 'claim_queued'
806
887
  ) {
807
- return new AbloClaimedError(message, { ...baseOpts, claims });
888
+ return new AbloClaimedError(message, { ...baseOpts, claims, conflict });
808
889
  }
809
890
  // A write whose `readAt` watermark went stale — callers re-read and retry.
810
891
  if (code === 'stale_context') {
@@ -854,6 +935,12 @@ const ENVELOPE_KEYS: ReadonlySet<string> = new Set([
854
935
  'requiredCapability',
855
936
  'claims',
856
937
  'heldByClaim',
938
+ 'heldBy',
939
+ 'heldByKind',
940
+ 'heldByClaimId',
941
+ 'heldByExpiresAt',
942
+ 'conflict',
943
+ 'target',
857
944
  // `reason` is deliberately NOT here: it is domain detail (a driver's words
858
945
  // beside the server's message), not an envelope field — the canonical
859
946
  // envelope schema has no such key — so it must survive into `details` for a
@@ -916,6 +1003,30 @@ export function translateHttpError(
916
1003
  : nested?.heldByClaim
917
1004
  ? [nested.heldByClaim]
918
1005
  : undefined);
1006
+ const conflict =
1007
+ parsed.conflict ??
1008
+ nested?.conflict ??
1009
+ normalizeClaimConflict({
1010
+ ...(parsed.heldBy ?? nested?.heldBy
1011
+ ? { heldBy: parsed.heldBy ?? nested?.heldBy }
1012
+ : {}),
1013
+ ...(parsed.heldByKind ?? nested?.heldByKind
1014
+ ? { heldByKind: parsed.heldByKind ?? nested?.heldByKind }
1015
+ : {}),
1016
+ ...(parsed.heldByClaimId ?? nested?.heldByClaimId
1017
+ ? { heldByClaimId: parsed.heldByClaimId ?? nested?.heldByClaimId }
1018
+ : {}),
1019
+ ...(parsed.heldByExpiresAt ?? nested?.heldByExpiresAt
1020
+ ? { heldByExpiresAt: parsed.heldByExpiresAt ?? nested?.heldByExpiresAt }
1021
+ : {}),
1022
+ ...(parsed.heldByClaim ?? nested?.heldByClaim
1023
+ ? { heldByClaim: parsed.heldByClaim ?? nested?.heldByClaim }
1024
+ : {}),
1025
+ ...(parsed.target ?? nested?.target
1026
+ ? { target: parsed.target ?? nested?.target }
1027
+ : {}),
1028
+ }) ??
1029
+ conflictFromClaims(claims);
919
1030
 
920
1031
  // The envelope carries `request_id`, so a caller that has only the body still
921
1032
  // gets the correlation id — without this it was dropped unless the caller
@@ -935,6 +1046,7 @@ export function translateHttpError(
935
1046
  requestId: requestId ?? (typeof bodyRequestId === 'string' ? bodyRequestId : undefined),
936
1047
  requiredCapability,
937
1048
  claims,
1049
+ conflict,
938
1050
  details: Object.keys(details).length > 0 ? details : undefined,
939
1051
  });
940
1052
  }
@@ -0,0 +1,108 @@
1
+ import { normalizeClaimConflict } from '../../claims/conflict.js';
2
+ import type { ClaimQueuedResponse } from '../../claims/contract.js';
3
+ import type { HttpClaimsResource } from '../../client/resources/httpResources.js';
4
+ import {
5
+ emitClaimStatus,
6
+ type ResolvedClaimContentionOptions,
7
+ } from '../../client/resources/modelOperations.js';
8
+ import { AbloClaimedError } from '../../errors.js';
9
+
10
+ const GRANT_POLL_FIRST_MS = 250;
11
+ const GRANT_POLL_INTERVAL_MS = 1_000;
12
+ const ignoreBestEffortClaimReleaseFailure = (): undefined => undefined;
13
+
14
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
15
+ return new Promise((resolve) => {
16
+ const done = (): void => {
17
+ clearTimeout(timer);
18
+ signal?.removeEventListener('abort', done);
19
+ resolve();
20
+ };
21
+ const timer = setTimeout(done, ms);
22
+ signal?.addEventListener('abort', done, { once: true });
23
+ });
24
+ }
25
+
26
+ /** Wait for a queued HTTP claim while keeping its server-side slot alive. */
27
+ export async function awaitClaimGrantOverHttp(
28
+ claims: HttpClaimsResource,
29
+ targetLabel: string,
30
+ queued: ClaimQueuedResponse,
31
+ options: ResolvedClaimContentionOptions,
32
+ ): Promise<{ id: string; fenceToken?: number }> {
33
+ const claimId = queued.id;
34
+ const { signal } = options;
35
+ const conflict = normalizeClaimConflict({
36
+ ...(queued.heldBy !== undefined ? { heldBy: queued.heldBy } : {}),
37
+ ...(queued.heldByKind !== undefined ? { heldByKind: queued.heldByKind } : {}),
38
+ ...(queued.heldByClaimId !== undefined ? { heldByClaimId: queued.heldByClaimId } : {}),
39
+ ...(queued.expiresAt !== undefined ? { heldByExpiresAt: queued.expiresAt } : {}),
40
+ ...(queued.heldByClaim !== undefined ? { heldByClaim: queued.heldByClaim } : {}),
41
+ });
42
+ const waitingError = (
43
+ message: string,
44
+ code: 'queue_too_deep' | 'claim_wait_aborted' | 'grant_timeout' | 'claim_lost',
45
+ ): AbloClaimedError => new AbloClaimedError(message, {
46
+ code,
47
+ claims: queued.heldByClaim ? [queued.heldByClaim] : undefined,
48
+ conflict,
49
+ });
50
+ const rejectAndLeave = async (error: AbloClaimedError): Promise<never> => {
51
+ await claims.release({ claimId }).catch(ignoreBestEffortClaimReleaseFailure);
52
+ throw error;
53
+ };
54
+
55
+ emitClaimStatus(options.onStatus, {
56
+ type: 'queued',
57
+ claimId,
58
+ position: queued.position,
59
+ ahead: queued.position + 1,
60
+ });
61
+ if (options.maxDepth !== undefined && queued.position >= options.maxDepth) {
62
+ return rejectAndLeave(waitingError(
63
+ `Claim queue for ${targetLabel} is ${queued.position} deep (max ${options.maxDepth}).`,
64
+ 'queue_too_deep',
65
+ ));
66
+ }
67
+
68
+ const deadline = options.timeoutMs !== undefined ? Date.now() + options.timeoutMs : undefined;
69
+ let delay = GRANT_POLL_FIRST_MS;
70
+ for (;;) {
71
+ if (signal?.aborted) {
72
+ return rejectAndLeave(waitingError(
73
+ `The wait for the claim on ${targetLabel} was aborted before the grant arrived.`,
74
+ 'claim_wait_aborted',
75
+ ));
76
+ }
77
+ if (deadline !== undefined && Date.now() >= deadline) {
78
+ return rejectAndLeave(waitingError(
79
+ `Timed out after ${options.timeoutMs}ms waiting for the queue grant on ${targetLabel}.`,
80
+ 'grant_timeout',
81
+ ));
82
+ }
83
+ await sleep(
84
+ deadline !== undefined ? Math.min(delay, Math.max(0, deadline - Date.now())) : delay,
85
+ signal,
86
+ );
87
+ if (signal?.aborted) {
88
+ return rejectAndLeave(waitingError(
89
+ `The wait for the claim on ${targetLabel} was aborted before the grant arrived.`,
90
+ 'claim_wait_aborted',
91
+ ));
92
+ }
93
+ delay = GRANT_POLL_INTERVAL_MS * (0.85 + Math.random() * 0.3);
94
+ const beat = await claims.heartbeat({ claimId });
95
+ if (beat.status !== 'held') continue;
96
+ const state = await claims.retrieve({ claimId });
97
+ if (state.status !== 'active') {
98
+ return rejectAndLeave(waitingError(
99
+ `Claim lost while queued for ${targetLabel}.`,
100
+ 'claim_lost',
101
+ ));
102
+ }
103
+ emitClaimStatus(options.onStatus, { type: 'granted', claimId, waited: true });
104
+ return state.fenceToken !== undefined
105
+ ? { id: claimId, fenceToken: state.fenceToken }
106
+ : { id: claimId };
107
+ }
108
+ }
@@ -19,8 +19,8 @@
19
19
  */
20
20
  import {
21
21
  createHttpTransport,
22
- type HttpTransport,
23
22
  } from './transport.js';
23
+ import type { HttpTransport } from './contract.js';
24
24
  import type { HttpClientConfig } from './options.js';
25
25
  import type {
26
26
  CommitResource,
@@ -272,6 +272,8 @@ const PROTOCOL_MEMBERS = new Set<string>([
272
272
  'getAuthToken',
273
273
  ]);
274
274
 
275
+ const ignoreAlreadySurfacedSessionOpeningFailure = (): undefined => undefined;
276
+
275
277
  /** Narrows a bare property name to a transport key so the facade can index it typed. */
276
278
  function isProtocolMember(prop: string): prop is keyof HttpTransport {
277
279
  return PROTOCOL_MEMBERS.has(prop);
@@ -555,9 +557,8 @@ export function createAbloHttpClient<S extends SchemaRecord>(
555
557
  return receipt;
556
558
  },
557
559
  dispatchClaim: async (input) => (await webSocketSession()).claim(input),
558
- releaseDispatchedClaim: (input) => {
559
- void webSocketSession().then((session) => session.release(input));
560
- },
560
+ releaseDispatchedClaim: async (input) =>
561
+ (await webSocketSession()).release(input),
561
562
  } : {}),
562
563
  });
563
564
  const schemaModels = new Set(Object.keys(schema.models));
@@ -614,7 +615,7 @@ export function createAbloHttpClient<S extends SchemaRecord>(
614
615
  webSocketOpen.abort();
615
616
  const opening = webSocketPromise;
616
617
  if (webSocket) await webSocket.close();
617
- else await opening?.then((session) => session.close()).catch(() => undefined);
618
+ else await opening?.then((session) => session.close()).catch(ignoreAlreadySurfacedSessionOpeningFailure);
618
619
  await transport.dispose();
619
620
  };
620
621
 
@@ -0,0 +1,71 @@
1
+ /** Private contract owned by the stateless HTTP transport boundary. */
2
+
3
+ import type { HttpClientConfig } from './options.js';
4
+ import type { CommitReceiptWire } from '../../commit/contract.js';
5
+ import type {
6
+ CommitResource,
7
+ HttpClaimsResource,
8
+ HttpLogsResource,
9
+ HttpTransportModel,
10
+ } from '../../client/resources/httpResources.js';
11
+ import type {
12
+ ClaimAcquired,
13
+ ClaimBeginPayload,
14
+ ClaimGranted,
15
+ ClaimQueued,
16
+ } from '../../coordination/schema.js';
17
+ import type { CoordinationObservability } from '../../observability.js';
18
+ import type { EffectiveAuthority } from '../../auth/capability.js';
19
+ import type { SessionAccess } from '../../sessions/index.js';
20
+ import type { CommitFrameOperation } from '../websocket/commitFrames.js';
21
+ import type { HttpReadOnChange } from './subscription.js';
22
+
23
+ /** @internal Private options for the schema-agnostic HTTP protocol transport. */
24
+ export type HttpTransportOptions = Omit<HttpClientConfig, 'schema'> & {
25
+ readonly bootstrapBaseUrl?: string | undefined;
26
+ /** Observability forwarded from `Ablo({ observability })`. */
27
+ readonly observability?: CoordinationObservability;
28
+ /** Per-request deadline in milliseconds. Pass `0` to disable it. */
29
+ readonly timeoutMs?: number;
30
+ /** @internal Routes writes through the selected duplex transport. */
31
+ readonly dispatchCommit?: ((input: {
32
+ readonly clientTxId: string;
33
+ readonly operations: readonly CommitFrameOperation[];
34
+ readonly reads?: readonly import('../../coordination/schema.js').ReadDependency[] | null;
35
+ }) => Promise<CommitReceiptWire>) | undefined;
36
+ /** @internal Routes held-claim acquisition through the shared socket. */
37
+ readonly dispatchClaim?: ((input: ClaimBeginPayload & {
38
+ readonly timeoutMs?: number;
39
+ readonly signal?: AbortSignal;
40
+ readonly onQueued?: (event: ClaimQueued) => Error | undefined;
41
+ }) => Promise<ClaimAcquired | ClaimGranted>) | undefined;
42
+ /** @internal Releases a socket-acquired claim on that same session. */
43
+ readonly releaseDispatchedClaim?: ((input: {
44
+ readonly claimId: string;
45
+ readonly entityType: string;
46
+ readonly entityId: string;
47
+ }) => Promise<void>) | undefined;
48
+ };
49
+
50
+ /** @internal Private protocol surface wrapped by `AbloHttpClient`. */
51
+ export interface HttpTransport {
52
+ ready(): Promise<void>;
53
+ waitForFlush(): Promise<void>;
54
+ /** Drains scheduled commits and active requests. */
55
+ dispose(): Promise<void>;
56
+ purge(): Promise<void>;
57
+ /** @internal Live check used only by `context().onChange`. */
58
+ readonly onChange: HttpReadOnChange;
59
+ readonly commits: CommitResource;
60
+ readonly claims: HttpClaimsResource;
61
+ readonly logs: HttpLogsResource;
62
+ /** Server-confirmed authority of the active bearer, populated by `ready()`. */
63
+ readonly identity: EffectiveAuthority | null;
64
+ model<T = Record<string, unknown>, Fields = T>(
65
+ name: string,
66
+ ): HttpTransportModel<T, Fields>;
67
+ /** Resolve the bearer credential used by this transport. */
68
+ getAuthToken(): Promise<string | null>;
69
+ /** @internal One normalized source shared by HTTP bootstrap and live transport. */
70
+ readonly access: SessionAccess;
71
+ }
@@ -1,5 +1,6 @@
1
1
  /** Stateless HTTP transport boundary. */
2
2
  export * from './options.js';
3
+ export * from './contract.js';
3
4
  export * from './client.js';
4
5
  export * from './transport.js';
5
6
  export * from './helpers.js';