@abloatai/humans 0.56.0 → 0.58.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 (130) hide show
  1. package/dist/Ablo.d.ts +8 -18
  2. package/dist/client.d.ts +6 -19
  3. package/dist/core.d.ts +1 -1
  4. package/dist/core.js +1 -1
  5. package/dist/local/Database.d.ts +1 -1
  6. package/dist/local/Model.js +1 -1
  7. package/dist/local/SyncClient.d.ts +3 -34
  8. package/dist/local/SyncClient.js +6 -16
  9. package/dist/local/client/createInternalComponents.d.ts +1 -1
  10. package/dist/local/client/createInternalComponents.js +1 -1
  11. package/dist/local/client/{createModelProxy.d.ts → createModelOperations.d.ts} +22 -9
  12. package/dist/local/client/{createModelProxy.js → createModelOperations.js} +341 -335
  13. package/dist/local/client/options.d.ts +2 -2
  14. package/dist/local/client/reactiveEngine.js +38 -45
  15. package/dist/local/client/resourceTypes.d.ts +3 -3
  16. package/dist/local/client/resourceTypes.js +1 -1
  17. package/dist/local/client/wsMutationExecutor.d.ts +4 -4
  18. package/dist/local/client/wsMutationExecutor.js +4 -5
  19. package/dist/local/coordination/ClaimLog.d.ts +3 -3
  20. package/dist/local/coordination/ClaimLog.js +2 -2
  21. package/dist/local/interfaces/index.d.ts +5 -7
  22. package/dist/local/mutators/mutateActions.d.ts +3 -2
  23. package/dist/local/query/types.d.ts +2 -2
  24. package/dist/local/stores/syncAction.d.ts +1 -1
  25. package/dist/local/stores/syncAction.js +1 -1
  26. package/dist/local/sync/ConnectionManager.d.ts +1 -1
  27. package/dist/local/sync/ConnectionManager.js +1 -1
  28. package/dist/local/sync/OnDemandLoader.d.ts +1 -1
  29. package/dist/local/sync/OnDemandLoader.js +1 -1
  30. package/dist/local/sync/SyncWebSocket.d.ts +3 -3
  31. package/dist/local/sync/SyncWebSocket.js +2 -2
  32. package/dist/local/sync/commitFrames.d.ts +1 -1
  33. package/dist/local/sync/commitFrames.js +1 -1
  34. package/dist/local/sync/contextOnChange.d.ts +8 -0
  35. package/dist/local/sync/contextOnChange.js +65 -0
  36. package/dist/local/sync/createClaimStream.d.ts +1 -1
  37. package/dist/local/sync/createClaimStream.js +47 -28
  38. package/dist/local/sync/credentialLifecycle.d.ts +1 -1
  39. package/dist/local/sync/credentialLifecycle.js +1 -1
  40. package/dist/local/sync/participants.d.ts +1 -1
  41. package/dist/local/sync/schemas.d.ts +2 -2
  42. package/dist/local/sync/schemas.js +1 -1
  43. package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
  44. package/dist/local/sync/wsFrameHandlers.js +1 -1
  45. package/dist/local/syncClientTypes.d.ts +41 -0
  46. package/dist/local/syncClientTypes.js +11 -0
  47. package/dist/local/transactions/databaseCommitOutbox.d.ts +2 -2
  48. package/dist/local/transactions/mutations/MutationQueue.d.ts +5 -18
  49. package/dist/local/transactions/mutations/MutationQueue.js +57 -137
  50. package/dist/local/transactions/mutations/batchProcessing.d.ts +2 -13
  51. package/dist/local/transactions/mutations/batchProcessing.js +0 -20
  52. package/dist/local/transactions/mutations/commitApi.d.ts +2 -3
  53. package/dist/local/transactions/mutations/commitApi.js +0 -4
  54. package/dist/local/transactions/mutations/commitLane.d.ts +6 -8
  55. package/dist/local/transactions/mutations/commitLane.js +12 -14
  56. package/dist/local/transactions/mutations/commitPayload.d.ts +2 -4
  57. package/dist/local/transactions/mutations/commitPayload.js +2 -6
  58. package/dist/local/transactions/mutations/commitTransport.d.ts +3 -4
  59. package/dist/local/transactions/mutations/commitTransport.js +2 -5
  60. package/dist/local/transactions/mutations/durableCommitRestore.d.ts +1 -1
  61. package/dist/local/transactions/mutations/durableCommitRestore.js +1 -4
  62. package/dist/local/transactions/mutations/durableWriteStore.d.ts +5 -5
  63. package/dist/local/transactions/mutations/durableWriteStore.js +3 -3
  64. package/dist/local/transactions/mutations/failurePolicy.d.ts +2 -2
  65. package/dist/local/transactions/mutations/mutationInput.d.ts +40 -0
  66. package/dist/local/transactions/mutations/mutationInput.js +53 -0
  67. package/dist/local/transactions/mutations/mutationPersistence.d.ts +1 -1
  68. package/dist/local/transactions/mutations/pendingDrain.d.ts +2 -2
  69. package/dist/local/transactions/mutations/replayValidation.d.ts +0 -20
  70. package/dist/local/transactions/mutations/replayValidation.js +1 -3
  71. package/dist/local/transactions/persistedTransaction.d.ts +0 -2
  72. package/dist/plugin.d.ts +2 -2
  73. package/dist/presenceStream.d.ts +1 -1
  74. package/dist/react/createAbloReact.d.ts +1 -1
  75. package/dist/react/useAblo.d.ts +2 -2
  76. package/dist/react/useAblo.js +1 -1
  77. package/dist/surface.d.ts +1 -1
  78. package/dist/surface.js +2 -2
  79. package/package.json +2 -2
  80. package/src/Ablo.ts +9 -27
  81. package/src/client.ts +6 -22
  82. package/src/core.ts +1 -1
  83. package/src/local/Database.ts +1 -2
  84. package/src/local/Model.ts +1 -1
  85. package/src/local/SyncClient.ts +17 -72
  86. package/src/local/client/createInternalComponents.ts +2 -2
  87. package/src/local/client/{createModelProxy.ts → createModelOperations.ts} +396 -320
  88. package/src/local/client/options.ts +2 -2
  89. package/src/local/client/reactiveEngine.ts +67 -55
  90. package/src/local/client/resourceTypes.ts +4 -4
  91. package/src/local/client/wsMutationExecutor.ts +4 -9
  92. package/src/local/coordination/ClaimLog.ts +3 -3
  93. package/src/local/interfaces/index.ts +5 -7
  94. package/src/local/mutators/mutateActions.ts +3 -2
  95. package/src/local/query/types.ts +2 -2
  96. package/src/local/stores/syncAction.ts +1 -1
  97. package/src/local/sync/ConnectionManager.ts +1 -1
  98. package/src/local/sync/OnDemandLoader.ts +1 -1
  99. package/src/local/sync/SyncWebSocket.ts +3 -3
  100. package/src/local/sync/commitFrames.ts +1 -2
  101. package/src/local/sync/contextOnChange.ts +92 -0
  102. package/src/local/sync/createClaimStream.ts +77 -39
  103. package/src/local/sync/credentialLifecycle.ts +1 -1
  104. package/src/local/sync/participants.ts +1 -1
  105. package/src/local/sync/schemas.ts +1 -1
  106. package/src/local/sync/wsFrameHandlers.ts +1 -1
  107. package/src/local/syncClientTypes.ts +59 -0
  108. package/src/local/transactions/databaseCommitOutbox.ts +2 -2
  109. package/src/local/transactions/mutations/MutationQueue.ts +69 -183
  110. package/src/local/transactions/mutations/batchProcessing.ts +2 -38
  111. package/src/local/transactions/mutations/commitApi.ts +3 -7
  112. package/src/local/transactions/mutations/commitLane.ts +24 -20
  113. package/src/local/transactions/mutations/commitPayload.ts +3 -11
  114. package/src/local/transactions/mutations/commitTransport.ts +3 -7
  115. package/src/local/transactions/mutations/durableCommitRestore.ts +2 -5
  116. package/src/local/transactions/mutations/durableWriteStore.ts +5 -5
  117. package/src/local/transactions/mutations/failurePolicy.ts +2 -2
  118. package/src/local/transactions/mutations/mutationInput.ts +69 -0
  119. package/src/local/transactions/mutations/mutationPersistence.ts +1 -1
  120. package/src/local/transactions/mutations/pendingDrain.ts +2 -2
  121. package/src/local/transactions/mutations/replayValidation.ts +1 -3
  122. package/src/local/transactions/persistedTransaction.ts +0 -3
  123. package/src/plugin.ts +2 -2
  124. package/src/presenceStream.ts +2 -2
  125. package/src/react/createAbloReact.ts +1 -1
  126. package/src/react/useAblo.ts +2 -2
  127. package/src/surface.ts +3 -3
  128. package/dist/local/sync/createSnapshot.d.ts +0 -29
  129. package/dist/local/sync/createSnapshot.js +0 -116
  130. package/src/local/sync/createSnapshot.ts +0 -160
@@ -19,8 +19,8 @@ import type { AbloPersistence } from '../persistence.js';
19
19
  import type {
20
20
  DurableWriteStore,
21
21
  DurableWritesConfig,
22
- } from '@abloatai/transaction/durableWrites';
23
- import type { CommitOutboxScope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
22
+ } from '@abloatai/transaction/commit';
23
+ import type { CommitOutboxScope } from '@abloatai/transaction/commit';
24
24
 
25
25
  // ── Options ───────────────────────────────────────────────────────────────
26
26
 
@@ -17,7 +17,7 @@ import { omittedModelError } from '@abloatai/transaction/schema/select';
17
17
  import {
18
18
  durableCommitOperationSchema,
19
19
  type DurableCommitOperation,
20
- } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
20
+ } from '@abloatai/transaction/commit';
21
21
  import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
22
22
  import type { ModelTarget, ModelClaim } from '@abloatai/transaction/coordination/schema';
23
23
  import type { BatchFence } from '@abloatai/transaction/coordination';
@@ -41,11 +41,14 @@ import type { StoreCluster } from './storeCluster.js';
41
41
  import { startStoreLifecycle } from './storeLifecycle.js';
42
42
  import type { SyncWebSocket, CoreSyncEventMap } from '../sync/SyncWebSocket.js';
43
43
  import { createClaimStream } from '../sync/createClaimStream.js';
44
- import { awaitClaimGrant } from '@abloatai/transaction/coordination/awaitClaimGrant';
45
- import { createSnapshot } from '../sync/createSnapshot.js';
44
+ import { awaitClaimGrant } from '@abloatai/transaction/claims';
45
+ import {
46
+ bindClaimLifetime,
47
+ claimLifetimeOf,
48
+ } from '@abloatai/transaction/claims/lifetime';
46
49
  import { createParticipantManager } from '../sync/participants.js';
47
50
  import type { AttachablePresenceStream } from '../../presenceStream.js';
48
- import type { ClaimWaitOptions, Snapshot } from '@abloatai/transaction/types/streams';
51
+ import type { ClaimWaitOptions } from '@abloatai/transaction/types/streams';
49
52
  import type { Claim } from '@abloatai/transaction/types/streams';
50
53
  import type { CredentialProvider } from '@abloatai/transaction/auth/apiKey';
51
54
  import { resolveApiKeyValue, resolveBootstrapBaseUrl } from '@abloatai/transaction/auth/apiKey';
@@ -66,9 +69,9 @@ import type {
66
69
  import {
67
70
  claimAttemptFailure,
68
71
  emitClaimStatus,
69
- } from '@abloatai/transaction/resources/modelOperations';
70
- import { createModelProxy, type ModelOperations } from './createModelProxy.js';
71
- import { assertWriteOptions } from '@abloatai/transaction/resources/writeOptionsSchema';
72
+ } from '@abloatai/transaction/client/resources/modelOperations';
73
+ import { createModelOperations, type ModelOperations } from './createModelOperations.js';
74
+ import { assertWriteOptions } from '@abloatai/transaction/client/resources/writeOptionsSchema';
72
75
  import type { AbloClient as Ablo } from '../../client.js';
73
76
  import {
74
77
  modelReadResponseSchema,
@@ -76,8 +79,17 @@ import {
76
79
  commitRecordListSchema,
77
80
  commitRecordWhereSchema,
78
81
  } from '@abloatai/transaction/wire';
79
- import { translateHttpError } from '@abloatai/transaction/errors';
80
- import { kReadEvidence } from '@abloatai/transaction/internal/read-set';
82
+ import {
83
+ translateHttpError,
84
+ type AbloStaleContextError,
85
+ } from '@abloatai/transaction/errors';
86
+ import {
87
+ kReadEvidence,
88
+ prepareReadSet,
89
+ } from '@abloatai/transaction/internal/read-set';
90
+ import { contextOnChange } from '../sync/contextOnChange.js';
91
+ import type { ReadDependency } from '@abloatai/transaction/coordination';
92
+ import type { CapturedRow } from '@abloatai/transaction/transport/http';
81
93
 
82
94
  /**
83
95
  * What the reactive build is fed: the factory's pass over the options bag
@@ -296,7 +308,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
296
308
 
297
309
  function normalizeCommitOperation(
298
310
  op: CommitOperationInput,
299
- defaults: Pick<CommitCreateOptions, 'readAt' | 'onStale'>,
311
+ defaults: Pick<CommitCreateOptions, 'readAt'>,
300
312
  fence: BatchFence | null,
301
313
  claim: Claim | null,
302
314
  ): DurableCommitOperation {
@@ -311,14 +323,13 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
311
323
  claimId:
312
324
  op.claimId ?? claimIdFor(claim?.target, claim?.id, op.model, op.id ?? null) ?? undefined,
313
325
  readAt: op.readAt ?? defaults.readAt ?? undefined,
314
- onStale: op.onStale ?? defaults.onStale ?? undefined,
315
326
  fenceToken:
316
327
  op.fenceToken ?? fenceTokenFor(fence, op.model, op.id ?? null) ?? undefined,
317
328
  });
318
329
  }
319
330
 
320
331
  function normalizeCommitOperations(
321
- commitOptions: CommitCreateOptions,
332
+ commitOptions: Pick<CommitCreateOptions, 'operations' | 'readAt' | 'claim'>,
322
333
  fence: BatchFence | null,
323
334
  ): DurableCommitOperation[] {
324
335
  if (commitOptions.operations.length === 0) {
@@ -457,7 +468,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
457
468
  // the one `awaitClaimGrant` read there; retain the handle fallback for
458
469
  // wire-compatible transports that already stamped it locally.
459
470
  const resolvedFenceToken = fenceToken ?? claim.fenceToken;
460
- return {
471
+ const wrapped = {
461
472
  object: 'claim',
462
473
  id: claim.id,
463
474
  description: claim.description,
@@ -472,7 +483,9 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
472
483
  // silently dropped from the public claim.
473
484
  heartbeat: claim.heartbeat,
474
485
  [Symbol.asyncDispose]: release,
475
- };
486
+ } satisfies Claim;
487
+ const lifetime = claimLifetimeOf(claim);
488
+ return lifetime ? bindClaimLifetime(wrapped, lifetime) : wrapped;
476
489
  }
477
490
 
478
491
  const publicClaims: ClaimResource = Object.assign(claimStream, {
@@ -580,7 +593,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
580
593
  const modelProxies: Record<string, ModelOperations<unknown, unknown>> = {};
581
594
  for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
582
595
  const registeredModelName = modelDef.typename ?? schemaKey;
583
- modelProxies[schemaKey] = createModelProxy(
596
+ modelProxies[schemaKey] = createModelOperations(
584
597
  schemaKey,
585
598
  registeredModelName,
586
599
  objectPool,
@@ -589,21 +602,15 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
589
602
  hydration,
590
603
  {
591
604
  createClaim: (claimOptions) => publicClaims.create(claimOptions),
605
+ // Lazily referenced: `commits` is declared below this loop, and this
606
+ // only runs when someone actually writes a batch.
607
+ commitBatch: (commitOptions) => commits.create(commitOptions),
592
608
  readPoint,
593
- createSnapshot: (modelKey, id) =>
594
- createSnapshot({
595
- pool: objectPool,
596
- transport,
597
- // `position.readFloor` is the value claims and snapshots stamp as
598
- // `readAt` (max of the pool-applied cursor and the acked
599
- // watermark for our own writes — see logPosition.ts).
600
- // Stamping a bare stream cursor made a claim taken right after
601
- // an ack-confirmed write stale against that write's own delta.
602
- // The socket/store cursors are persistence-gated and therefore
603
- // never ahead of `applied` — no extra max() needed here.
604
- getLastSyncId: () => syncClient.position.readFloor,
605
- entities: { [modelKey]: id },
606
- }),
609
+ // A claim needs the post-read watermark, not the legacy snapshot
610
+ // object. `readFloor` is max(applied, acked-own-write), so a claim
611
+ // taken immediately after its own confirmed mutation is not stale
612
+ // against an echo that has not materialised locally yet.
613
+ currentReadAt: () => syncClient.position.readFloor,
607
614
  queue: (target) =>
608
615
  publicClaims.queueFor(streamTarget(target)),
609
616
  reorder: (target, order) =>
@@ -653,31 +660,39 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
653
660
  );
654
661
  }
655
662
 
656
- const commits: CommitResource = {
657
- async create(commitOptions: CommitCreateOptions): Promise<CommitReceipt> {
663
+ const commits: CommitResource<ReadDependency | CapturedRow> = {
664
+ async create(
665
+ commitOptions: CommitCreateOptions<ReadDependency | CapturedRow>,
666
+ ): Promise<CommitReceipt> {
658
667
  await ready();
668
+ const prepared = prepareReadSet(
669
+ cluster.readSetContext,
670
+ syncClient as object,
671
+ commitOptions.readAt,
672
+ commitOptions.idempotencyKey,
673
+ commitOptions.reads,
674
+ );
659
675
  // Same runtime contract as the per-model writes — one schema.
660
676
  assertWriteOptions(
661
677
  {
662
- idempotencyKey: commitOptions.idempotencyKey,
663
- readAt: commitOptions.readAt,
664
- onStale: commitOptions.onStale,
678
+ idempotencyKey: prepared.idempotencyKey,
679
+ readAt: prepared.readAt,
665
680
  wait: commitOptions.wait,
666
681
  claim: commitOptions.claim,
682
+ reads: prepared.reads,
667
683
  },
668
684
  'commits.create',
669
685
  );
670
- const clientTxId = createClientTxId(commitOptions.idempotencyKey);
686
+ const clientTxId = createClientTxId(prepared.idempotencyKey);
671
687
  // A claim handle supplies the batch stale-guard defaults — same
672
688
  // semantics as `ablo.<model>.update({ id, data, claim })`, so the
673
689
  // two write doors speak one claim vocabulary. Explicit options win.
674
690
  const claim = commitOptions.claim ?? null;
675
691
  const operations = normalizeCommitOperations(
676
692
  {
677
- ...commitOptions,
678
- readAt: commitOptions.readAt ?? claim?.readAt ?? null,
679
- onStale:
680
- commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
693
+ operations: commitOptions.operations,
694
+ ...(commitOptions.claim !== undefined ? { claim: commitOptions.claim } : {}),
695
+ readAt: prepared.readAt ?? claim?.readAt ?? null,
681
696
  },
682
697
  batchFence(claim?.target, claim?.fenceToken),
683
698
  );
@@ -693,22 +708,21 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
693
708
  // no need to leak an accessor through BaseSyncedStore.
694
709
  const queue = syncClient.getMutationQueue();
695
710
  await queue.enqueueCommit(clientTxId, operations, {
696
- ...(commitOptions.reads ? { reads: [...commitOptions.reads] } : {}),
697
- ...(commitOptions.track ? { track: [...commitOptions.track] } : {}),
711
+ ...(prepared.reads ? { reads: [...prepared.reads] } : {}),
698
712
  });
699
713
 
700
714
  if (wait === 'queued') {
701
715
  return { id: clientTxId, status: 'queued' };
702
716
  }
703
717
 
704
- const { lastSyncId, notifications, missingIds } =
718
+ const { lastSyncId, missingIds, operationResults } =
705
719
  await queue.waitForCommitReceipt(clientTxId);
706
720
  return {
707
721
  id: clientTxId,
708
722
  status: 'confirmed',
709
723
  lastSyncId,
710
- ...(notifications && notifications.length > 0 ? { notifications } : {}),
711
724
  ...(missingIds && missingIds.length > 0 ? { missingIds } : {}),
725
+ ...(operationResults && operationResults.length > 0 ? { operationResults } : {}),
712
726
  };
713
727
  },
714
728
  async get({ id }) {
@@ -901,6 +915,7 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
901
915
  const sessionParams = {
902
916
  agent: { id },
903
917
  can: params.can,
918
+ ...(params.onBehalfOf ? { onBehalfOf: params.onBehalfOf } : {}),
904
919
  ...(params.syncGroups ? { syncGroups: params.syncGroups } : {}),
905
920
  ...(params.ttlSeconds !== undefined ? { ttlSeconds: params.ttlSeconds } : {}),
906
921
  ...(userMeta ? { userMeta } : {}),
@@ -1014,21 +1029,18 @@ export function buildReactiveEngine<const S extends SchemaRecord>(
1014
1029
 
1015
1030
  commits,
1016
1031
 
1017
- /** Context-staleness snapshot — see `engine.snapshot(...)` JSDoc. */
1018
- snapshot<ModelName extends keyof S & string>(
1019
- entities: Readonly<Record<ModelName, string | readonly string[]>>,
1020
- ): Snapshot<Schema<S>, ModelName> {
1021
- return createSnapshot<Schema<S>, ModelName>({
1022
- pool: objectPool,
1023
- transport,
1024
- getLastSyncId: () => transport.getLastSyncId(),
1025
- entities,
1026
- });
1027
- },
1028
1032
  } as Ablo<S>;
1029
1033
 
1030
1034
  Object.defineProperty(engine, kReadEvidence, {
1031
- value: { context: cluster.readSetContext, client: syncClient as object },
1035
+ value: {
1036
+ context: cluster.readSetContext,
1037
+ client: syncClient as object,
1038
+ onChange: (
1039
+ reads: readonly ReadDependency[],
1040
+ listener: (error: AbloStaleContextError) => void,
1041
+ ) =>
1042
+ contextOnChange(syncClient, objectPool, reads, listener),
1043
+ },
1032
1044
  enumerable: false,
1033
1045
  });
1034
1046
 
@@ -8,7 +8,7 @@
8
8
  * live participant handle.
9
9
  */
10
10
 
11
- export * from '@abloatai/transaction/resources/httpResources';
11
+ export * from '@abloatai/transaction/client/resources/httpResources';
12
12
 
13
13
  // The request contract for `ablo.<model>` — also core, re-joined here so this
14
14
  // module stays the single import path for the whole resource surface.
@@ -17,7 +17,7 @@ export type {
17
17
  LocalReadOptions,
18
18
  ModelListScope,
19
19
  ServerReadOptions,
20
- ModelRetrieveParams,
20
+ ModelReadParams,
21
21
  ModelWriteOptions,
22
22
  ModelCreateParams,
23
23
  ModelUpdateParams,
@@ -34,8 +34,8 @@ export type {
34
34
  ClaimHeartbeatOptions,
35
35
  HeldClaim,
36
36
  HeldLease,
37
- } from '@abloatai/transaction/resources/modelOperations';
37
+ } from '@abloatai/transaction/client/resources/modelOperations';
38
38
 
39
39
  // `ModelOperations` binds the request contract to reactive model instances, so
40
40
  // it lives with the factory that builds it rather than with the core.
41
- export type { ModelOperations } from './createModelProxy.js';
41
+ export type { ModelOperations } from './createModelOperations.js';
@@ -5,19 +5,19 @@
5
5
  * client wires this up automatically unless you supply your own executor.
6
6
  */
7
7
 
8
- import type { ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
8
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
9
9
  import type {
10
10
  MutationExecutor,
11
11
  MutationOperation,
12
12
  MutationOptions,
13
13
  } from '../interfaces/index.js';
14
14
  import type { CommitAck } from '../sync/commitFrames.js';
15
- import type { CommitReceiptWire } from '@abloatai/transaction/wire/commit';
15
+ import type { CommitReceiptWire } from '@abloatai/transaction/commit';
16
16
  import {
17
17
  recordWebSocketCommitReceipt,
18
18
  type ReadSetContext,
19
19
  } from '@abloatai/transaction/internal/read-set';
20
- import { commitAckSchema } from '@abloatai/transaction/wire/commit';
20
+ import { commitAckSchema } from '@abloatai/transaction/commit';
21
21
  import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
22
22
 
23
23
  // ── Default mutation executor (wire: `commit` frame over WebSocket) ──────
@@ -44,14 +44,12 @@ import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
44
44
  clientTxId: string,
45
45
  timeoutMs?: number,
46
46
  reads?: readonly ReadDependency[] | null,
47
- track?: readonly TrackDependency[] | null,
48
47
  ) => Promise<CommitReceiptWire>;
49
48
  sendCommit?: (
50
49
  operations: readonly MutationOperation[],
51
50
  clientTxId: string,
52
51
  timeoutMs?: number,
53
52
  reads?: readonly ReadDependency[] | null,
54
- track?: readonly TrackDependency[] | null,
55
53
  ) => Promise<CommitAck>;
56
54
  } | null,
57
55
  readSetContext?: ReadSetContext,
@@ -80,21 +78,19 @@ import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
80
78
  clientTxId,
81
79
  undefined,
82
80
  options?.reads,
83
- options?.track,
84
81
  );
85
82
  recordWebSocketCommitReceipt(readSetContext, {
86
83
  receipt,
87
84
  operations,
88
85
  reads: options?.reads,
89
- track: options?.track,
90
86
  });
91
87
  return commitAckSchema.parse({
92
88
  status: receipt.status,
93
89
  statusAt: receipt.statusAt,
94
90
  lastSyncId: receipt.lastSyncId,
95
91
  ...(receipt.correlationId ? { correlationId: receipt.correlationId } : {}),
96
- ...(receipt.notifications ? { notifications: receipt.notifications } : {}),
97
92
  ...(receipt.missingIds ? { missingIds: receipt.missingIds } : {}),
93
+ ...(receipt.operationResults ? { operationResults: receipt.operationResults } : {}),
98
94
  });
99
95
  }
100
96
  if (!ws.sendCommit) {
@@ -107,7 +103,6 @@ import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
107
103
  clientTxId,
108
104
  undefined, // use sendCommit's built-in 15s default; no per-call override
109
105
  options?.reads,
110
- options?.track,
111
106
  );
112
107
  } catch (err) {
113
108
  // Wrap transport-level failures as connection errors so the transaction
@@ -6,11 +6,11 @@
6
6
  * without leaking bootstrap, storage, or WebSocket vocabulary into the core.
7
7
  */
8
8
 
9
- import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/coordination/trace';
9
+ import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/claims';
10
10
  import type { ObservabilityProvider } from '../interfaces/index.js';
11
11
 
12
- export { formatClaim, formatConflict } from '@abloatai/transaction/coordination/trace';
13
- export type { ClaimLogEntry } from '@abloatai/transaction/coordination/trace';
12
+ export { formatClaim, formatConflict } from '@abloatai/transaction/claims';
13
+ export type { ClaimLogEntry } from '@abloatai/transaction/claims';
14
14
 
15
15
  export class ClaimLog extends CoordinationClaimLog implements ObservabilityProvider {
16
16
  // Client-lifecycle hooks the claim log has no notion of. Every body below is
@@ -7,9 +7,8 @@
7
7
  * mutations to your backend. The SDK ships sensible no-op defaults where it can.
8
8
  */
9
9
 
10
- import type { MutationCommitResultInput } from '@abloatai/transaction/wire/commit';
11
- import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
12
- export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/coordination/events';
10
+ import type { MutationCommitResultInput } from '@abloatai/transaction/commit';
11
+ export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/claims/events';
13
12
  import type { CoordinationObservability } from '@abloatai/transaction/observability';
14
13
  export type { CoordinationObservability } from '@abloatai/transaction/observability';
15
14
 
@@ -231,8 +230,8 @@ export type MutationCommitResult = MutationCommitResultInput;
231
230
  // re-exports without binding X in this module's scope, and `Pick<X, K>` on an
232
231
  // unbound X silently yields all-required properties rather than a missing-name
233
232
  // error at the Pick site.
234
- export type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
235
- import type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
233
+ export type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
234
+ import type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
236
235
 
237
236
  /**
238
237
  * The subset of {@link MutationOptions} that travels with each write as it is
@@ -243,7 +242,7 @@ import type { MutationOptions } from '@abloatai/transaction/resources/mutationOp
243
242
  */
244
243
  export type WriteOptions = Pick<
245
244
  MutationOptions,
246
- 'readAt' | 'onStale' | 'idempotencyKey' | 'label' | 'fenceToken' | 'claimRef'
245
+ 'readAt' | 'idempotencyKey' | 'label' | 'fenceToken' | 'claimRef'
247
246
  >;
248
247
 
249
248
  /** A single mutation within a batch. Its `options` travel with it so the server
@@ -268,7 +267,6 @@ export interface MutationOperation {
268
267
  /** Server-issued claim identity the operation is attributed to. */
269
268
  claimId?: string | null;
270
269
  readAt?: number | null;
271
- onStale?: OnStaleMode | null;
272
270
  /**
273
271
  * The fencing token (Option B) carried on the wire for this op — the held
274
272
  * claim's token, validated against the entity's high-water at commit.
@@ -23,8 +23,9 @@ type UpdatePatch<S extends Schema, K extends keyof S['models'] & string> =
23
23
 
24
24
  export interface MutateActions<S extends Schema, K extends keyof S['models'] & string> {
25
25
  /**
26
- * Create one entity, or an array of entities in a single tick. ID,
27
- * createdAt, updatedAt, organizationId default automatically per row.
26
+ * Create one entity, or an array of entities in a single tick. The id and
27
+ * the tenancy value default per row; every other column, audit timestamps
28
+ * included, comes from the declared fields.
28
29
  */
29
30
  create(data: InferCreate<S, K>): Promise<InferModel<S, K>>;
30
31
  create(data: InferCreate<S, K>[]): Promise<InferModel<S, K>[]>;
@@ -22,8 +22,8 @@ export type {
22
22
  WhereOp,
23
23
  WhereClause,
24
24
  LoadWhere,
25
- } from '@abloatai/transaction/resources/where';
26
- import type { WhereClause } from '@abloatai/transaction/resources/where';
25
+ } from '@abloatai/transaction/client/resources/where';
26
+ import type { WhereClause } from '@abloatai/transaction/client/resources/where';
27
27
  import type { ModelListEvidence } from '@abloatai/transaction/wire';
28
28
 
29
29
  /** A single structured fetch request. */
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { z } from 'zod';
9
- import { syncDeltaActionSchema } from '@abloatai/transaction/wire/delta';
9
+ import { syncDeltaActionSchema } from '@abloatai/transaction/observation';
10
10
 
11
11
  export const syncActionSchema = z.object({
12
12
  id: z.number(),
@@ -12,4 +12,4 @@ export {
12
12
  type ConnectionEvent,
13
13
  type ConnectionCallbacks,
14
14
  type ConnectionManagerOptions,
15
- } from '@abloatai/transaction/transport/connectionManager';
15
+ } from '@abloatai/transaction/transport/connection';
@@ -38,7 +38,7 @@ import type { RuntimeContext } from '../RuntimeContext.js';
38
38
  import { postQuery } from '../query/client.js';
39
39
  import type { RecoveryClass } from '@abloatai/transaction/errorCodes';
40
40
  import type { LoadWhere, Query, WhereClause, WhereOp } from '../query/types.js';
41
- import { normalizeWhere } from '@abloatai/transaction/resources/where';
41
+ import { normalizeWhere } from '@abloatai/transaction/client/resources/where';
42
42
  import type { Schema } from '@abloatai/transaction/schema/schema';
43
43
  import type { LogPositionPort } from '../logPosition.js';
44
44
 
@@ -11,14 +11,14 @@
11
11
  */
12
12
 
13
13
  import { getContext } from '../context.js';
14
- import { clientSyncDeltaSchema, type ClientSyncDelta } from '@abloatai/transaction/wire/delta';
14
+ import { clientSyncDeltaSchema, type ClientSyncDelta } from '@abloatai/transaction/observation';
15
15
  import { drainProfilingEnabled, observeDrainStage } from './drainProfile.js';
16
16
  import {
17
17
  WsTransport,
18
18
  type WsTransportOptions,
19
19
  type EventMap,
20
20
  type DefaultCollaborationEvents,
21
- } from '@abloatai/transaction/transport/wsTransport';
21
+ } from '@abloatai/transaction/transport/websocket';
22
22
  import { isRecord } from './wsFrameHandlers.js';
23
23
  // Sync-position state (lastSyncId watermark, version vector, server cursor).
24
24
  import { SyncCursor } from './syncCursor.js';
@@ -41,7 +41,7 @@ export type {
41
41
  DefaultCollaborationEvents,
42
42
  EventMap,
43
43
  SyncWebSocketEventMap,
44
- } from '@abloatai/transaction/transport/wsTransport';
44
+ } from '@abloatai/transaction/transport/websocket';
45
45
 
46
46
  /**
47
47
  * How often, while connected, the client polls for any deltas whose best-effort
@@ -8,9 +8,8 @@
8
8
 
9
9
  export {
10
10
  buildCommitFrame,
11
- parseNotifications,
12
11
  recordClaim,
13
12
  type CommitAck,
14
13
  type CommitFrameOperation,
15
14
  type ClaimTracePorts,
16
- } from '@abloatai/transaction/transport/commitFrames';
15
+ } from '@abloatai/transaction/transport/websocket';
@@ -0,0 +1,92 @@
1
+ import type { ReadDependency } from '@abloatai/transaction/coordination';
2
+ import { AbloStaleContextError } from '@abloatai/transaction/errors';
3
+ import type { InstanceCache } from '../InstanceCache.js';
4
+ import type { SyncDelta } from './SyncWebSocket.js';
5
+
6
+ export interface ContextOnChangeTransport {
7
+ subscribe(event: string, listener: (value?: unknown) => void): () => void;
8
+ }
9
+
10
+ /** Match context reads on the reactive client's existing delta connection. */
11
+ export function contextOnChange(
12
+ transport: ContextOnChangeTransport,
13
+ pool: Pick<InstanceCache, 'peek' | 'watermarks'>,
14
+ reads: readonly ReadDependency[],
15
+ listener: (error: AbloStaleContextError) => void,
16
+ ): () => void {
17
+ let stopped = false;
18
+ const rowReads = reads.filter(
19
+ (read): read is Extract<ReadDependency, { model: string }> => 'model' in read,
20
+ );
21
+
22
+ const stopDelta = transport.subscribe('delta', (value) => {
23
+ if (isDelta(value)) changedBy([value]);
24
+ });
25
+ const stopBatch = transport.subscribe('delta_batch', (value) => {
26
+ if (Array.isArray(value)) changedBy(value.filter(isDelta));
27
+ });
28
+
29
+ function stop(): void {
30
+ if (stopped) return;
31
+ stopped = true;
32
+ stopDelta();
33
+ stopBatch();
34
+ }
35
+
36
+ function changedBy(deltas: readonly SyncDelta[]): void {
37
+ if (stopped) return;
38
+ for (const delta of deltas) {
39
+ const read = rowReads.find(
40
+ (candidate) =>
41
+ candidate.model.toLowerCase() === delta.modelName.toLowerCase() &&
42
+ candidate.id === delta.modelId &&
43
+ delta.id > candidate.readAt,
44
+ );
45
+ if (!read) continue;
46
+ stop();
47
+ listener(staleError(read.model, read.id, read.readAt, delta.id));
48
+ return;
49
+ }
50
+ }
51
+
52
+ // Subscribe first, then inspect the resident row. A delta that lands between
53
+ // the original read and this attachment is either observed above or has
54
+ // already advanced this exact row in the pool.
55
+ for (const read of rowReads) {
56
+ const resident = pool.peek(read.id);
57
+ if (!resident || resident.getModelName().toLowerCase() !== read.model.toLowerCase()) {
58
+ continue;
59
+ }
60
+ const observed = pool.watermarks.of(resident);
61
+ if (observed !== undefined && observed > read.readAt) {
62
+ stop();
63
+ listener(staleError(read.model, read.id, read.readAt, observed));
64
+ break;
65
+ }
66
+ }
67
+
68
+ return stop;
69
+ }
70
+
71
+ function staleError(
72
+ model: string,
73
+ id: string,
74
+ readAt: number,
75
+ observedSyncId: number,
76
+ ): AbloStaleContextError {
77
+ return new AbloStaleContextError('Context changed after read.', {
78
+ code: 'stale_context',
79
+ readAt,
80
+ conflicts: [{ model, id, observedSyncId }],
81
+ });
82
+ }
83
+
84
+ function isDelta(value: unknown): value is SyncDelta {
85
+ return (
86
+ typeof value === 'object' &&
87
+ value !== null &&
88
+ typeof (value as { id?: unknown }).id === 'number' &&
89
+ typeof (value as { modelName?: unknown }).modelName === 'string' &&
90
+ typeof (value as { modelId?: unknown }).modelId === 'string'
91
+ );
92
+ }