@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
@@ -7,8 +7,8 @@
7
7
  import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
8
8
  import type { RuntimeConfig, Logger, MutationExecutor, ObservabilityProvider, Analytics, SessionErrorDetector, OnlineStatusProvider } from '../interfaces/index.js';
9
9
  import type { AbloPersistence } from '../persistence.js';
10
- import type { DurableWriteStore, DurableWritesConfig } from '@abloatai/transaction/durableWrites';
11
- import type { CommitOutboxScope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
10
+ import type { DurableWriteStore, DurableWritesConfig } from '@abloatai/transaction/commit';
11
+ import type { CommitOutboxScope } from '@abloatai/transaction/commit';
12
12
  /**
13
13
  * An async function that resolves an apiKey at request time. Use it for credential
14
14
  * rotation — read from a vault, refresh from session storage, or pull from an
@@ -12,7 +12,7 @@
12
12
  * design step (docs/plans/package-split.md).
13
13
  */
14
14
  import { omittedModelError } from '@abloatai/transaction/schema/select';
15
- import { durableCommitOperationSchema, } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
15
+ import { durableCommitOperationSchema, } from '@abloatai/transaction/commit';
16
16
  import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
17
17
  import { batchFence, claimIdFor, fenceTokenFor, modelTarget, streamTarget, subTarget, } from '@abloatai/transaction/coordination';
18
18
  import { validateAbloOptions } from './validateAbloOptions.js';
@@ -21,16 +21,17 @@ import { revokeCapability, rotateCapability, } from '@abloatai/transaction/auth/
21
21
  import { modelWireNames } from '@abloatai/transaction/auth/capability';
22
22
  import { startStoreLifecycle } from './storeLifecycle.js';
23
23
  import { createClaimStream } from '../sync/createClaimStream.js';
24
- import { awaitClaimGrant } from '@abloatai/transaction/coordination/awaitClaimGrant';
25
- import { createSnapshot } from '../sync/createSnapshot.js';
24
+ import { awaitClaimGrant } from '@abloatai/transaction/claims';
25
+ import { bindClaimLifetime, claimLifetimeOf, } from '@abloatai/transaction/claims/lifetime';
26
26
  import { createParticipantManager } from '../sync/participants.js';
27
27
  import { resolveApiKeyValue, resolveBootstrapBaseUrl } from '@abloatai/transaction/auth/apiKey';
28
- import { claimAttemptFailure, emitClaimStatus, } from '@abloatai/transaction/resources/modelOperations';
29
- import { createModelProxy } from './createModelProxy.js';
30
- import { assertWriteOptions } from '@abloatai/transaction/resources/writeOptionsSchema';
28
+ import { claimAttemptFailure, emitClaimStatus, } from '@abloatai/transaction/client/resources/modelOperations';
29
+ import { createModelOperations } from './createModelOperations.js';
30
+ import { assertWriteOptions } from '@abloatai/transaction/client/resources/writeOptionsSchema';
31
31
  import { modelReadResponseSchema, commitRecordSchema, commitRecordListSchema, commitRecordWhereSchema, } from '@abloatai/transaction/wire';
32
- import { translateHttpError } from '@abloatai/transaction/errors';
33
- import { kReadEvidence } from '@abloatai/transaction/internal/read-set';
32
+ import { translateHttpError, } from '@abloatai/transaction/errors';
33
+ import { kReadEvidence, prepareReadSet, } from '@abloatai/transaction/internal/read-set';
34
+ import { contextOnChange } from '../sync/contextOnChange.js';
34
35
  export function buildReactiveEngine(inputs) {
35
36
  const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials, transport, participantId, kind, presence, cluster, createSibling, } = inputs;
36
37
  const schema = options.schema;
@@ -186,7 +187,6 @@ export function buildReactiveEngine(inputs) {
186
187
  transactionId: op.transactionId ?? undefined,
187
188
  claimId: op.claimId ?? claimIdFor(claim?.target, claim?.id, op.model, op.id ?? null) ?? undefined,
188
189
  readAt: op.readAt ?? defaults.readAt ?? undefined,
189
- onStale: op.onStale ?? defaults.onStale ?? undefined,
190
190
  fenceToken: op.fenceToken ?? fenceTokenFor(fence, op.model, op.id ?? null) ?? undefined,
191
191
  });
192
192
  }
@@ -292,7 +292,7 @@ export function buildReactiveEngine(inputs) {
292
292
  // the one `awaitClaimGrant` read there; retain the handle fallback for
293
293
  // wire-compatible transports that already stamped it locally.
294
294
  const resolvedFenceToken = fenceToken ?? claim.fenceToken;
295
- return {
295
+ const wrapped = {
296
296
  object: 'claim',
297
297
  id: claim.id,
298
298
  description: claim.description,
@@ -308,6 +308,8 @@ export function buildReactiveEngine(inputs) {
308
308
  heartbeat: claim.heartbeat,
309
309
  [Symbol.asyncDispose]: release,
310
310
  };
311
+ const lifetime = claimLifetimeOf(claim);
312
+ return lifetime ? bindClaimLifetime(wrapped, lifetime) : wrapped;
311
313
  }
312
314
  const publicClaims = Object.assign(claimStream, {
313
315
  async create(claimOptions) {
@@ -406,22 +408,17 @@ export function buildReactiveEngine(inputs) {
406
408
  const modelProxies = {};
407
409
  for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
408
410
  const registeredModelName = modelDef.typename ?? schemaKey;
409
- modelProxies[schemaKey] = createModelProxy(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
411
+ modelProxies[schemaKey] = createModelOperations(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
410
412
  createClaim: (claimOptions) => publicClaims.create(claimOptions),
413
+ // Lazily referenced: `commits` is declared below this loop, and this
414
+ // only runs when someone actually writes a batch.
415
+ commitBatch: (commitOptions) => commits.create(commitOptions),
411
416
  readPoint,
412
- createSnapshot: (modelKey, id) => createSnapshot({
413
- pool: objectPool,
414
- transport,
415
- // `position.readFloor` is the value claims and snapshots stamp as
416
- // `readAt` (max of the pool-applied cursor and the acked
417
- // watermark for our own writes — see logPosition.ts).
418
- // Stamping a bare stream cursor made a claim taken right after
419
- // an ack-confirmed write stale against that write's own delta.
420
- // The socket/store cursors are persistence-gated and therefore
421
- // never ahead of `applied` — no extra max() needed here.
422
- getLastSyncId: () => syncClient.position.readFloor,
423
- entities: { [modelKey]: id },
424
- }),
417
+ // A claim needs the post-read watermark, not the legacy snapshot
418
+ // object. `readFloor` is max(applied, acked-own-write), so a claim
419
+ // taken immediately after its own confirmed mutation is not stale
420
+ // against an echo that has not materialised locally yet.
421
+ currentReadAt: () => syncClient.position.readFloor,
425
422
  queue: (target) => publicClaims.queueFor(streamTarget(target)),
426
423
  reorder: (target, order) => { publicClaims.reorder(streamTarget(target), order); },
427
424
  // One row can have several holders — sub-row claims on disjoint
@@ -466,23 +463,24 @@ export function buildReactiveEngine(inputs) {
466
463
  const commits = {
467
464
  async create(commitOptions) {
468
465
  await ready();
466
+ const prepared = prepareReadSet(cluster.readSetContext, syncClient, commitOptions.readAt, commitOptions.idempotencyKey, commitOptions.reads);
469
467
  // Same runtime contract as the per-model writes — one schema.
470
468
  assertWriteOptions({
471
- idempotencyKey: commitOptions.idempotencyKey,
472
- readAt: commitOptions.readAt,
473
- onStale: commitOptions.onStale,
469
+ idempotencyKey: prepared.idempotencyKey,
470
+ readAt: prepared.readAt,
474
471
  wait: commitOptions.wait,
475
472
  claim: commitOptions.claim,
473
+ reads: prepared.reads,
476
474
  }, 'commits.create');
477
- const clientTxId = createClientTxId(commitOptions.idempotencyKey);
475
+ const clientTxId = createClientTxId(prepared.idempotencyKey);
478
476
  // A claim handle supplies the batch stale-guard defaults — same
479
477
  // semantics as `ablo.<model>.update({ id, data, claim })`, so the
480
478
  // two write doors speak one claim vocabulary. Explicit options win.
481
479
  const claim = commitOptions.claim ?? null;
482
480
  const operations = normalizeCommitOperations({
483
- ...commitOptions,
484
- readAt: commitOptions.readAt ?? claim?.readAt ?? null,
485
- onStale: commitOptions.onStale ?? (claim?.readAt !== undefined ? 'reject' : null),
481
+ operations: commitOptions.operations,
482
+ ...(commitOptions.claim !== undefined ? { claim: commitOptions.claim } : {}),
483
+ readAt: prepared.readAt ?? claim?.readAt ?? null,
486
484
  }, batchFence(claim?.target, claim?.fenceToken));
487
485
  const wait = commitOptions.wait ?? 'confirmed';
488
486
  // Route through the MutationQueue's commit lane so the call
@@ -496,19 +494,18 @@ export function buildReactiveEngine(inputs) {
496
494
  // no need to leak an accessor through BaseSyncedStore.
497
495
  const queue = syncClient.getMutationQueue();
498
496
  await queue.enqueueCommit(clientTxId, operations, {
499
- ...(commitOptions.reads ? { reads: [...commitOptions.reads] } : {}),
500
- ...(commitOptions.track ? { track: [...commitOptions.track] } : {}),
497
+ ...(prepared.reads ? { reads: [...prepared.reads] } : {}),
501
498
  });
502
499
  if (wait === 'queued') {
503
500
  return { id: clientTxId, status: 'queued' };
504
501
  }
505
- const { lastSyncId, notifications, missingIds } = await queue.waitForCommitReceipt(clientTxId);
502
+ const { lastSyncId, missingIds, operationResults } = await queue.waitForCommitReceipt(clientTxId);
506
503
  return {
507
504
  id: clientTxId,
508
505
  status: 'confirmed',
509
506
  lastSyncId,
510
- ...(notifications && notifications.length > 0 ? { notifications } : {}),
511
507
  ...(missingIds && missingIds.length > 0 ? { missingIds } : {}),
508
+ ...(operationResults && operationResults.length > 0 ? { operationResults } : {}),
512
509
  };
513
510
  },
514
511
  async get({ id }) {
@@ -677,6 +674,7 @@ export function buildReactiveEngine(inputs) {
677
674
  const sessionParams = {
678
675
  agent: { id },
679
676
  can: params.can,
677
+ ...(params.onBehalfOf ? { onBehalfOf: params.onBehalfOf } : {}),
680
678
  ...(params.syncGroups ? { syncGroups: params.syncGroups } : {}),
681
679
  ...(params.ttlSeconds !== undefined ? { ttlSeconds: params.ttlSeconds } : {}),
682
680
  ...(userMeta ? { userMeta } : {}),
@@ -764,18 +762,13 @@ export function buildReactiveEngine(inputs) {
764
762
  /** Claim livestream — same socket. Stable reference. */
765
763
  claims: publicClaims,
766
764
  commits,
767
- /** Context-staleness snapshot — see `engine.snapshot(...)` JSDoc. */
768
- snapshot(entities) {
769
- return createSnapshot({
770
- pool: objectPool,
771
- transport,
772
- getLastSyncId: () => transport.getLastSyncId(),
773
- entities,
774
- });
775
- },
776
765
  };
777
766
  Object.defineProperty(engine, kReadEvidence, {
778
- value: { context: cluster.readSetContext, client: syncClient },
767
+ value: {
768
+ context: cluster.readSetContext,
769
+ client: syncClient,
770
+ onChange: (reads, listener) => contextOnChange(syncClient, objectPool, reads, listener),
771
+ },
779
772
  enumerable: false,
780
773
  });
781
774
  // A model the schema projection left out answers with an error naming the
@@ -7,6 +7,6 @@
7
7
  * that stay here — `ModelOperations` chief among them, because it returns the
8
8
  * live participant handle.
9
9
  */
10
- export * from '@abloatai/transaction/resources/httpResources';
11
- export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelWriteOptions, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimContentionOptions, ClaimAttemptEvent, ClaimQueueView, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, } from '@abloatai/transaction/resources/modelOperations';
12
- export type { ModelOperations } from './createModelProxy.js';
10
+ export * from '@abloatai/transaction/client/resources/httpResources';
11
+ export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelReadParams, ModelWriteOptions, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimContentionOptions, ClaimAttemptEvent, ClaimQueueView, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, } from '@abloatai/transaction/client/resources/modelOperations';
12
+ export type { ModelOperations } from './createModelOperations.js';
@@ -7,4 +7,4 @@
7
7
  * that stay here — `ModelOperations` chief among them, because it returns the
8
8
  * live participant handle.
9
9
  */
10
- export * from '@abloatai/transaction/resources/httpResources';
10
+ export * from '@abloatai/transaction/client/resources/httpResources';
@@ -4,10 +4,10 @@
4
4
  * commit time because it does not exist yet when the executor is created. A
5
5
  * client wires this up automatically unless you supply your own executor.
6
6
  */
7
- import type { ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
7
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
8
8
  import type { MutationExecutor, MutationOperation } from '../interfaces/index.js';
9
9
  import type { CommitAck } from '../sync/commitFrames.js';
10
- import type { CommitReceiptWire } from '@abloatai/transaction/wire/commit';
10
+ import type { CommitReceiptWire } from '@abloatai/transaction/commit';
11
11
  import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
12
12
  /**
13
13
  * Creates the default mutation executor, which sends each change as a `commit`
@@ -25,6 +25,6 @@ import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
25
25
  * generation below exists only for direct, one-shot executor consumers.
26
26
  */
27
27
  export declare function createDefaultMutationExecutor(getWs: () => {
28
- sendCommitReceipt?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null, track?: readonly TrackDependency[] | null) => Promise<CommitReceiptWire>;
29
- sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null, track?: readonly TrackDependency[] | null) => Promise<CommitAck>;
28
+ sendCommitReceipt?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null) => Promise<CommitReceiptWire>;
29
+ sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null) => Promise<CommitAck>;
30
30
  } | null, readSetContext?: ReadSetContext): MutationExecutor;
@@ -5,7 +5,7 @@
5
5
  * client wires this up automatically unless you supply your own executor.
6
6
  */
7
7
  import { recordWebSocketCommitReceipt, } from '@abloatai/transaction/internal/read-set';
8
- import { commitAckSchema } from '@abloatai/transaction/wire/commit';
8
+ import { commitAckSchema } from '@abloatai/transaction/commit';
9
9
  import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
10
10
  // ── Default mutation executor (wire: `commit` frame over WebSocket) ──────
11
11
  /**
@@ -36,20 +36,19 @@ export function createDefaultMutationExecutor(getWs, readSetContext) {
36
36
  : `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`);
37
37
  try {
38
38
  if (ws.sendCommitReceipt) {
39
- const receipt = await ws.sendCommitReceipt(operations, clientTxId, undefined, options?.reads, options?.track);
39
+ const receipt = await ws.sendCommitReceipt(operations, clientTxId, undefined, options?.reads);
40
40
  recordWebSocketCommitReceipt(readSetContext, {
41
41
  receipt,
42
42
  operations,
43
43
  reads: options?.reads,
44
- track: options?.track,
45
44
  });
46
45
  return commitAckSchema.parse({
47
46
  status: receipt.status,
48
47
  statusAt: receipt.statusAt,
49
48
  lastSyncId: receipt.lastSyncId,
50
49
  ...(receipt.correlationId ? { correlationId: receipt.correlationId } : {}),
51
- ...(receipt.notifications ? { notifications: receipt.notifications } : {}),
52
50
  ...(receipt.missingIds ? { missingIds: receipt.missingIds } : {}),
51
+ ...(receipt.operationResults ? { operationResults: receipt.operationResults } : {}),
53
52
  });
54
53
  }
55
54
  if (!ws.sendCommit) {
@@ -58,7 +57,7 @@ export function createDefaultMutationExecutor(getWs, readSetContext) {
58
57
  });
59
58
  }
60
59
  return await ws.sendCommit(operations, clientTxId, undefined, // use sendCommit's built-in 15s default; no per-call override
61
- options?.reads, options?.track);
60
+ options?.reads);
62
61
  }
63
62
  catch (err) {
64
63
  // Wrap transport-level failures as connection errors so the transaction
@@ -5,10 +5,10 @@
5
5
  * `ObservabilityProvider`, preserving `Ablo({ observability: new ClaimLog() })`
6
6
  * without leaking bootstrap, storage, or WebSocket vocabulary into the core.
7
7
  */
8
- import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/coordination/trace';
8
+ import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/claims';
9
9
  import type { ObservabilityProvider } from '../interfaces/index.js';
10
- export { formatClaim, formatConflict } from '@abloatai/transaction/coordination/trace';
11
- export type { ClaimLogEntry } from '@abloatai/transaction/coordination/trace';
10
+ export { formatClaim, formatConflict } from '@abloatai/transaction/claims';
11
+ export type { ClaimLogEntry } from '@abloatai/transaction/claims';
12
12
  export declare class ClaimLog extends CoordinationClaimLog implements ObservabilityProvider {
13
13
  setContext(): void;
14
14
  setConnectionState(): void;
@@ -5,8 +5,8 @@
5
5
  * `ObservabilityProvider`, preserving `Ablo({ observability: new ClaimLog() })`
6
6
  * without leaking bootstrap, storage, or WebSocket vocabulary into the core.
7
7
  */
8
- import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/coordination/trace';
9
- export { formatClaim, formatConflict } from '@abloatai/transaction/coordination/trace';
8
+ import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/claims';
9
+ export { formatClaim, formatConflict } from '@abloatai/transaction/claims';
10
10
  export class ClaimLog extends CoordinationClaimLog {
11
11
  // Client-lifecycle hooks the claim log has no notion of. Every body below is
12
12
  // deliberately inert: the log records claims and conflicts, nothing else.
@@ -6,9 +6,8 @@
6
6
  * session-error detection, online-status checks, and the transport that carries
7
7
  * mutations to your backend. The SDK ships sensible no-op defaults where it can.
8
8
  */
9
- import type { MutationCommitResultInput } from '@abloatai/transaction/wire/commit';
10
- import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
11
- export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/coordination/events';
9
+ import type { MutationCommitResultInput } from '@abloatai/transaction/commit';
10
+ export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/claims/events';
12
11
  import type { CoordinationObservability } from '@abloatai/transaction/observability';
13
12
  export type { CoordinationObservability } from '@abloatai/transaction/observability';
14
13
  export type { Logger } from '@abloatai/transaction/logger';
@@ -133,8 +132,8 @@ export interface ModelDebugLoggerContract {
133
132
  * while every explicit queued result requires a WAL correlation.
134
133
  */
135
134
  export type MutationCommitResult = MutationCommitResultInput;
136
- export type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
137
- import type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
135
+ export type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
136
+ import type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
138
137
  /**
139
138
  * The subset of {@link MutationOptions} that travels with each write as it is
140
139
  * queued offline and sent on the wire. A single shared type keeps the public
@@ -142,7 +141,7 @@ import type { MutationOptions } from '@abloatai/transaction/resources/mutationOp
142
141
  * `claim` are deliberately absent: both are resolved on the client before a write
143
142
  * is staged, so neither reaches this layer.
144
143
  */
145
- export type WriteOptions = Pick<MutationOptions, 'readAt' | 'onStale' | 'idempotencyKey' | 'label' | 'fenceToken' | 'claimRef'>;
144
+ export type WriteOptions = Pick<MutationOptions, 'readAt' | 'idempotencyKey' | 'label' | 'fenceToken' | 'claimRef'>;
146
145
  /** A single mutation within a batch. Its `options` travel with it so the server
147
146
  * can cache and replay the operation for idempotent retries. */
148
147
  export interface MutationOperation {
@@ -165,7 +164,6 @@ export interface MutationOperation {
165
164
  /** Server-issued claim identity the operation is attributed to. */
166
165
  claimId?: string | null;
167
166
  readAt?: number | null;
168
- onStale?: OnStaleMode | null;
169
167
  /**
170
168
  * The fencing token (Option B) carried on the wire for this op — the held
171
169
  * claim's token, validated against the entity's high-water at commit.
@@ -17,8 +17,9 @@ type UpdatePatch<S extends Schema, K extends keyof S['models'] & string> = {
17
17
  } & Partial<InferModel<S, K>>;
18
18
  export interface MutateActions<S extends Schema, K extends keyof S['models'] & string> {
19
19
  /**
20
- * Create one entity, or an array of entities in a single tick. ID,
21
- * createdAt, updatedAt, organizationId default automatically per row.
20
+ * Create one entity, or an array of entities in a single tick. The id and
21
+ * the tenancy value default per row; every other column, audit timestamps
22
+ * included, comes from the declared fields.
22
23
  */
23
24
  create(data: InferCreate<S, K>): Promise<InferModel<S, K>>;
24
25
  create(data: InferCreate<S, K>[]): Promise<InferModel<S, K>[]>;
@@ -13,8 +13,8 @@
13
13
  * key — for example, fetching every block whose `sectionId` falls in a set of
14
14
  * ids.
15
15
  */
16
- export type { WherePrimitive, WhereOp, WhereClause, LoadWhere, } from '@abloatai/transaction/resources/where';
17
- import type { WhereClause } from '@abloatai/transaction/resources/where';
16
+ export type { WherePrimitive, WhereOp, WhereClause, LoadWhere, } from '@abloatai/transaction/client/resources/where';
17
+ import type { WhereClause } from '@abloatai/transaction/client/resources/where';
18
18
  import type { ModelListEvidence } from '@abloatai/transaction/wire';
19
19
  /** A single structured fetch request. */
20
20
  export interface Query {
@@ -10,10 +10,10 @@ export declare const syncActionSchema: z.ZodObject<{
10
10
  modelName: z.ZodString;
11
11
  modelId: z.ZodString;
12
12
  action: z.ZodEnum<{
13
+ A: "A";
13
14
  I: "I";
14
15
  U: "U";
15
16
  D: "D";
16
- A: "A";
17
17
  V: "V";
18
18
  C: "C";
19
19
  G: "G";
@@ -5,7 +5,7 @@
5
5
  * schema, while this one adds the local `__class` discriminator IndexedDB uses.
6
6
  */
7
7
  import { z } from 'zod';
8
- import { syncDeltaActionSchema } from '@abloatai/transaction/wire/delta';
8
+ import { syncDeltaActionSchema } from '@abloatai/transaction/observation';
9
9
  export const syncActionSchema = z.object({
10
10
  id: z.number(),
11
11
  modelName: z.string(),
@@ -5,4 +5,4 @@
5
5
  * unchanged; the reactive store mirrors its transitions through
6
6
  * `ConnectionCallbacks.onStateChange` into its own observable `syncStatus`.
7
7
  */
8
- export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '@abloatai/transaction/transport/connectionManager';
8
+ export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '@abloatai/transaction/transport/connection';
@@ -5,4 +5,4 @@
5
5
  * unchanged; the reactive store mirrors its transitions through
6
6
  * `ConnectionCallbacks.onStateChange` into its own observable `syncStatus`.
7
7
  */
8
- export { ConnectionManager, } from '@abloatai/transaction/transport/connectionManager';
8
+ export { ConnectionManager, } from '@abloatai/transaction/transport/connection';
@@ -34,7 +34,7 @@ import type { ModelRegistry } from '../ModelRegistry.js';
34
34
  import type { RuntimeContext } from '../RuntimeContext.js';
35
35
  import type { RecoveryClass } from '@abloatai/transaction/errorCodes';
36
36
  import type { LoadWhere, WhereClause } from '../query/types.js';
37
- import { normalizeWhere } from '@abloatai/transaction/resources/where';
37
+ import { normalizeWhere } from '@abloatai/transaction/client/resources/where';
38
38
  import type { Schema } from '@abloatai/transaction/schema/schema';
39
39
  import type { LogPositionPort } from '../logPosition.js';
40
40
  export interface OnDemandLoaderOptions {
@@ -30,7 +30,7 @@
30
30
  import { ModelScope } from '../InstanceCache.js';
31
31
  import { AbloValidationError } from '@abloatai/transaction/errors';
32
32
  import { postQuery } from '../query/client.js';
33
- import { normalizeWhere } from '@abloatai/transaction/resources/where';
33
+ import { normalizeWhere } from '@abloatai/transaction/client/resources/where';
34
34
  const LOCAL = { kind: 'local' };
35
35
  /**
36
36
  * The position a returned row provably reflects: the greater of its own
@@ -9,10 +9,10 @@
9
9
  * and open/close hooks with the materialisation the transport deliberately
10
10
  * does not do.
11
11
  */
12
- import { type ClientSyncDelta } from '@abloatai/transaction/wire/delta';
13
- import { WsTransport, type WsTransportOptions, type EventMap, type DefaultCollaborationEvents } from '@abloatai/transaction/transport/wsTransport';
12
+ import { type ClientSyncDelta } from '@abloatai/transaction/observation';
13
+ import { WsTransport, type WsTransportOptions, type EventMap, type DefaultCollaborationEvents } from '@abloatai/transaction/transport/websocket';
14
14
  export type { CommitAck } from './commitFrames.js';
15
- export type { SyncCapabilities, BootstrapHint, BootstrapDataEvent, PresenceUpdate, CoreSyncEventMap, DefaultCollaborationEvents, EventMap, SyncWebSocketEventMap, } from '@abloatai/transaction/transport/wsTransport';
15
+ export type { SyncCapabilities, BootstrapHint, BootstrapDataEvent, PresenceUpdate, CoreSyncEventMap, DefaultCollaborationEvents, EventMap, SyncWebSocketEventMap, } from '@abloatai/transaction/transport/websocket';
16
16
  /**
17
17
  * The wire delta the client receives. It is inferred from the canonical
18
18
  * `clientSyncDeltaSchema` so the client and server share one contract rather
@@ -10,9 +10,9 @@
10
10
  * does not do.
11
11
  */
12
12
  import { getContext } from '../context.js';
13
- import { clientSyncDeltaSchema } from '@abloatai/transaction/wire/delta';
13
+ import { clientSyncDeltaSchema } from '@abloatai/transaction/observation';
14
14
  import { drainProfilingEnabled, observeDrainStage } from './drainProfile.js';
15
- import { WsTransport, } from '@abloatai/transaction/transport/wsTransport';
15
+ import { WsTransport, } from '@abloatai/transaction/transport/websocket';
16
16
  import { isRecord } from './wsFrameHandlers.js';
17
17
  // Sync-position state (lastSyncId watermark, version vector, server cursor).
18
18
  import { SyncCursor } from './syncCursor.js';
@@ -5,4 +5,4 @@
5
5
  * takes the structural `CommitFrameOperation` slice, which this package's
6
6
  * `MutationOperation` satisfies.
7
7
  */
8
- export { buildCommitFrame, parseNotifications, recordClaim, type CommitAck, type CommitFrameOperation, type ClaimTracePorts, } from '@abloatai/transaction/transport/commitFrames';
8
+ export { buildCommitFrame, recordClaim, type CommitAck, type CommitFrameOperation, type ClaimTracePorts, } from '@abloatai/transaction/transport/websocket';
@@ -5,4 +5,4 @@
5
5
  * takes the structural `CommitFrameOperation` slice, which this package's
6
6
  * `MutationOperation` satisfies.
7
7
  */
8
- export { buildCommitFrame, parseNotifications, recordClaim, } from '@abloatai/transaction/transport/commitFrames';
8
+ export { buildCommitFrame, recordClaim, } from '@abloatai/transaction/transport/websocket';
@@ -0,0 +1,8 @@
1
+ import type { ReadDependency } from '@abloatai/transaction/coordination';
2
+ import { AbloStaleContextError } from '@abloatai/transaction/errors';
3
+ import type { InstanceCache } from '../InstanceCache.js';
4
+ export interface ContextOnChangeTransport {
5
+ subscribe(event: string, listener: (value?: unknown) => void): () => void;
6
+ }
7
+ /** Match context reads on the reactive client's existing delta connection. */
8
+ export declare function contextOnChange(transport: ContextOnChangeTransport, pool: Pick<InstanceCache, 'peek' | 'watermarks'>, reads: readonly ReadDependency[], listener: (error: AbloStaleContextError) => void): () => void;
@@ -0,0 +1,65 @@
1
+ import { AbloStaleContextError } from '@abloatai/transaction/errors';
2
+ /** Match context reads on the reactive client's existing delta connection. */
3
+ export function contextOnChange(transport, pool, reads, listener) {
4
+ let stopped = false;
5
+ const rowReads = reads.filter((read) => 'model' in read);
6
+ const stopDelta = transport.subscribe('delta', (value) => {
7
+ if (isDelta(value))
8
+ changedBy([value]);
9
+ });
10
+ const stopBatch = transport.subscribe('delta_batch', (value) => {
11
+ if (Array.isArray(value))
12
+ changedBy(value.filter(isDelta));
13
+ });
14
+ function stop() {
15
+ if (stopped)
16
+ return;
17
+ stopped = true;
18
+ stopDelta();
19
+ stopBatch();
20
+ }
21
+ function changedBy(deltas) {
22
+ if (stopped)
23
+ return;
24
+ for (const delta of deltas) {
25
+ const read = rowReads.find((candidate) => candidate.model.toLowerCase() === delta.modelName.toLowerCase() &&
26
+ candidate.id === delta.modelId &&
27
+ delta.id > candidate.readAt);
28
+ if (!read)
29
+ continue;
30
+ stop();
31
+ listener(staleError(read.model, read.id, read.readAt, delta.id));
32
+ return;
33
+ }
34
+ }
35
+ // Subscribe first, then inspect the resident row. A delta that lands between
36
+ // the original read and this attachment is either observed above or has
37
+ // already advanced this exact row in the pool.
38
+ for (const read of rowReads) {
39
+ const resident = pool.peek(read.id);
40
+ if (!resident || resident.getModelName().toLowerCase() !== read.model.toLowerCase()) {
41
+ continue;
42
+ }
43
+ const observed = pool.watermarks.of(resident);
44
+ if (observed !== undefined && observed > read.readAt) {
45
+ stop();
46
+ listener(staleError(read.model, read.id, read.readAt, observed));
47
+ break;
48
+ }
49
+ }
50
+ return stop;
51
+ }
52
+ function staleError(model, id, readAt, observedSyncId) {
53
+ return new AbloStaleContextError('Context changed after read.', {
54
+ code: 'stale_context',
55
+ readAt,
56
+ conflicts: [{ model, id, observedSyncId }],
57
+ });
58
+ }
59
+ function isDelta(value) {
60
+ return (typeof value === 'object' &&
61
+ value !== null &&
62
+ typeof value.id === 'number' &&
63
+ typeof value.modelName === 'string' &&
64
+ typeof value.modelId === 'string');
65
+ }
@@ -21,7 +21,7 @@
21
21
  * • Inbound `claim_rejected` — the server refused the claim, with conflict
22
22
  * metadata.
23
23
  */
24
- import type { WsTransport } from '@abloatai/transaction/transport/wsTransport';
24
+ import type { WsTransport } from '@abloatai/transaction/transport/websocket';
25
25
  import type { ClaimOptions, Claim, ClaimStream, PresenceTarget } from '@abloatai/transaction/types/streams';
26
26
  import { type Logger } from '@abloatai/transaction/logger';
27
27
  /**