@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
@@ -24,10 +24,11 @@
24
24
  import { asyncIteratorFrom } from '@abloatai/transaction/utils/asyncIterator';
25
25
  import { toMs } from '@abloatai/transaction/utils/duration';
26
26
  import { claimDescription, descriptionFromMeta, participantKindFromWire, } from '@abloatai/transaction/coordination/schema';
27
- import { isTargetTuple, subTarget, streamTarget, wireTarget, } from '@abloatai/transaction/coordination/locator';
28
- import { declaredMeta } from '@abloatai/transaction/coordination/claimMeta';
27
+ import { isTargetTuple, subTarget, streamTarget, wireTarget, } from '@abloatai/transaction/claims';
28
+ import { bindClaimLifetime, createClaimLifetime, } from '@abloatai/transaction/claims/lifetime';
29
+ import { declaredMeta } from '@abloatai/transaction/claims';
29
30
  import { AbloClaimedError, AbloConnectionError } from '@abloatai/transaction/errors';
30
- import { resolveHeartbeatOptions } from '@abloatai/transaction/coordination/claimHeartbeatLoop';
31
+ import { resolveHeartbeatOptions } from '@abloatai/transaction/claims';
31
32
  import { noopLogger } from '@abloatai/transaction/logger';
32
33
  /** Readable target for the coordination trace: `documents:abc` / `documents:abc.title`. */
33
34
  function claimLabel(type, id, field) {
@@ -47,7 +48,7 @@ export function createClaimStream(config, transport = null) {
47
48
  // ── State: others' open claims, keyed by claimId ───────────────
48
49
  const activeByClaimId = new Map();
49
50
  let claimsSnapshot = Object.freeze([]);
50
- // ── State: our own open claims (for re-announce on reconnect) ───
51
+ // ── State: our own open grants, keyed by the exact claim id ─────
51
52
  const ownClaims = new Map();
52
53
  // ── State: per-entity wait queues, from `claim_queue` frames ────
53
54
  // Keyed `type:id`; the value is the FIFO line of queued claims. Powers
@@ -157,15 +158,18 @@ export function createClaimStream(config, transport = null) {
157
158
  unsubs.push(t.subscribe('claim_rejected', (rejection) => {
158
159
  if (!rejection.claimId)
159
160
  return;
160
- if (ownClaims.has(rejection.claimId)) {
161
+ const own = ownClaims.get(rejection.claimId);
162
+ if (own) {
161
163
  const tgt = rejection.target
162
164
  ? claimLabel(rejection.target.entityType, rejection.target.entityId, rejection.target.field)
163
165
  : rejection.claimId;
164
166
  logger.info(`claim: rejected ${tgt}${rejection.heldBy ? ` — held by ${rejection.heldBy}` : ''}`, { claimId: rejection.claimId, reason: rejection.reason });
165
167
  }
166
- // Drop the rejected own-claim so reconnect doesn't re-announce
167
- // a claim the server already rejected (would just spam both
168
- // sides with conflicts).
168
+ own?.lifetime.end(new AbloClaimedError(rejection.message ?? `Claim ${rejection.claimId} was rejected.`, {
169
+ code: rejection.reason === 'conflict'
170
+ ? 'claim_conflict'
171
+ : 'claim_lease_unavailable',
172
+ }));
169
173
  ownClaims.delete(rejection.claimId);
170
174
  // A holder on another server may have claimed before this client joined
171
175
  // the row group, so its one-shot presence frame was missed. A conflict
@@ -193,12 +197,12 @@ export function createClaimStream(config, transport = null) {
193
197
  // The frame is validated by the transport's dispatcher against the same
194
198
  // schema this parameter's type comes from, so it arrives proven.
195
199
  t.subscribe('claim_lost', (lost) => {
196
- if (ownClaims.has(lost.claimId)) {
197
- const c = ownClaims.get(lost.claimId);
198
- logger.info(`claim: lost ${c ? claimLabel(c.entityType, c.entityId, c.field) : lost.claimId} (preempted or expired)`, { claimId: lost.claimId });
200
+ const own = ownClaims.get(lost.claimId);
201
+ if (own) {
202
+ const c = own.claim;
203
+ logger.info(`claim: lost ${claimLabel(c.entityType, c.entityId, c.field)} (preempted or expired)`, { claimId: lost.claimId });
199
204
  }
200
- // Drop the lost own-claim so reconnect doesn't re-announce a lease we
201
- // no longer hold.
205
+ own?.lifetime.end(new AbloClaimedError(`Claim ${lost.claimId} was lost because it ${lost.reason === 'expired' ? 'expired' : 'was preempted'}. Re-acquire it before continuing.`, { code: 'claim_lost' }));
202
206
  ownClaims.delete(lost.claimId);
203
207
  for (const l of lostListeners) {
204
208
  try {
@@ -260,17 +264,27 @@ export function createClaimStream(config, transport = null) {
260
264
  });
261
265
  return;
262
266
  }
263
- const c = ownClaims.get(ack.claimId);
264
- reject(new AbloClaimedError(`The lease behind ${c ? claimLabel(c.entityType, c.entityId, c.field) : `claim ${ack.claimId}`} is no longer held — it expired or was granted onward while this participant was working. Re-acquire the claim and retry; a write attempted under the old lease is rejected by its \`readAt\` guard.`, { code: 'claim_lost' }));
267
+ const own = ownClaims.get(ack.claimId);
268
+ const c = own?.claim;
269
+ const error = new AbloClaimedError(`The lease behind ${c ? claimLabel(c.entityType, c.entityId, c.field) : `claim ${ack.claimId}`} is no longer held — it expired or was granted onward while this participant was working. Re-acquire the claim and retry; a write attempted under the old lease is rejected by its \`readAt\` guard.`, { code: 'claim_lost' });
270
+ own?.lifetime.end(error);
271
+ ownClaims.delete(ack.claimId);
272
+ reject(error);
265
273
  });
266
274
  }));
267
- // (3) On reconnect, re-announce every open self-claim the
268
- // server's claim state is in-memory and is lost across
269
- // restarts. Without this, peers would see our claims vanish
270
- // whenever the connection blipped.
271
- unsubs.push(t.subscribe('connected', () => {
272
- for (const [claimId, claim] of ownClaims) {
273
- sendBegin(claimId, claim);
275
+ // (3) A granted handle belongs to this connection's exact grant. The server
276
+ // releases the participant's claims when its last socket leaves; trying
277
+ // to recreate them on `connected` silently swapped the lease beneath an
278
+ // old `data` / `readAt` / fenceToken tuple. End them instead. A caller
279
+ // reconnects, re-reads, and takes a fresh handle.
280
+ unsubs.push(t.subscribe('disconnected', () => {
281
+ const error = new AbloConnectionError('The connection holding this claim ended. Reconnect and acquire a fresh claim before continuing.');
282
+ for (const { lifetime } of ownClaims.values()) {
283
+ lifetime.end(error);
284
+ }
285
+ ownClaims.clear();
286
+ for (const claimId of [...pendingHeartbeats.keys()]) {
287
+ settleHeartbeat(claimId, ({ reject }) => reject(error));
274
288
  }
275
289
  }));
276
290
  }
@@ -308,14 +322,14 @@ export function createClaimStream(config, transport = null) {
308
322
  }
309
323
  /**
310
324
  * Send one heartbeat and await its ack. Rejects with
311
- * {@link AbloConnectionError} (transient the auto-heartbeat loop retries
312
- * on its next tick) when the socket is down or the ack times out, and with
325
+ * {@link AbloConnectionError} when the socket is down or the ack times out,
326
+ * and with
313
327
  * {@link AbloClaimedError} (definitive) when the server answers that the
314
328
  * lease is no longer ours.
315
329
  */
316
330
  function sendHeartbeat(claimId, claim, options) {
317
331
  if (!attached?.isConnected()) {
318
- return Promise.reject(new AbloConnectionError(`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down. The keepalive renews held leases automatically on reconnect; the next beat retries.`));
332
+ return Promise.reject(new AbloConnectionError(`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down. This connection's grant has ended; reconnect and acquire a fresh claim.`));
319
333
  }
320
334
  return new Promise((resolve, reject) => {
321
335
  settleHeartbeat(claimId, ({ reject: rejectPrior }) => {
@@ -371,7 +385,8 @@ export function createClaimStream(config, transport = null) {
371
385
  estimatedMs,
372
386
  queue: args.queue,
373
387
  };
374
- ownClaims.set(claimId, claim);
388
+ const lifetime = createClaimLifetime();
389
+ ownClaims.set(claimId, { claim, lifetime });
375
390
  sendBegin(claimId, claim);
376
391
  // Coordination trace (info): the creator can see their human/agent claims.
377
392
  logger.info(`claim: requesting ${claimLabel(claim.entityType, claim.entityId, claim.field)} for "${claim.description}"` +
@@ -381,11 +396,12 @@ export function createClaimStream(config, transport = null) {
381
396
  if (revoked)
382
397
  return;
383
398
  revoked = true;
399
+ lifetime.end();
384
400
  ownClaims.delete(claimId);
385
401
  sendAbandon(claimId, claim);
386
402
  logger.info(`claim: released ${claimLabel(claim.entityType, claim.entityId, claim.field)}`, { claimId });
387
403
  };
388
- return {
404
+ return bindClaimLifetime({
389
405
  object: 'claim',
390
406
  id: claimId,
391
407
  status: 'active',
@@ -403,7 +419,7 @@ export function createClaimStream(config, transport = null) {
403
419
  [Symbol.asyncDispose]: async () => {
404
420
  revoke();
405
421
  },
406
- };
422
+ }, lifetime);
407
423
  }
408
424
  function resolveTarget(target) {
409
425
  if (isTargetTuple(target))
@@ -471,6 +487,9 @@ export function createClaimStream(config, transport = null) {
471
487
  reject(new AbloConnectionError('The claim stream was disposed while this heartbeat was awaiting its reply.'));
472
488
  });
473
489
  }
490
+ const error = new AbloConnectionError('The claim stream was disposed. Acquire a fresh claim before continuing.');
491
+ for (const { lifetime } of ownClaims.values())
492
+ lifetime.end(error);
474
493
  listeners.clear();
475
494
  rejectionListeners.clear();
476
495
  lostListeners.clear();
@@ -4,4 +4,4 @@
4
4
  * as much as a browser does. This path re-exports it so existing importers
5
5
  * stay unchanged.
6
6
  */
7
- export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/transport/credentialLifecycle';
7
+ export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/transport/connection';
@@ -4,4 +4,4 @@
4
4
  * as much as a browser does. This path re-exports it so existing importers
5
5
  * stay unchanged.
6
6
  */
7
- export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/transport/credentialLifecycle';
7
+ export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/transport/connection';
@@ -1,4 +1,4 @@
1
- import type { JoinOptions } from '@abloatai/transaction/resources/modelOperations';
1
+ import type { JoinOptions } from '@abloatai/transaction/client/resources/modelOperations';
2
2
  import type { SyncWebSocket } from './SyncWebSocket.js';
3
3
  import type { Schema } from '@abloatai/transaction/schema/schema';
4
4
  import type { Claim, Activity, ClaimTarget, ClaimStream, Peer, PresenceStream, PresenceTarget } from '@abloatai/transaction/types/streams';
@@ -20,10 +20,10 @@ export declare const ServerDeltaSchema: z.ZodObject<{
20
20
  id: z.ZodNumber;
21
21
  data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
22
22
  actionType: z.ZodEnum<{
23
+ A: "A";
23
24
  I: "I";
24
25
  U: "U";
25
26
  D: "D";
26
- A: "A";
27
27
  V: "V";
28
28
  C: "C";
29
29
  G: "G";
@@ -44,10 +44,10 @@ export declare const BootstrapResponseSchema: z.ZodObject<{
44
44
  id: z.ZodNumber;
45
45
  data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
46
46
  actionType: z.ZodEnum<{
47
+ A: "A";
47
48
  I: "I";
48
49
  U: "U";
49
50
  D: "D";
50
- A: "A";
51
51
  V: "V";
52
52
  C: "C";
53
53
  G: "G";
@@ -8,7 +8,7 @@
8
8
  import { z } from 'zod';
9
9
  import { globalRuntime } from "../context.js";
10
10
  import { AbloValidationError } from "@abloatai/transaction/errors";
11
- import { syncDeltaWireCoreSchema } from '@abloatai/transaction/wire/delta';
11
+ import { syncDeltaWireCoreSchema } from '@abloatai/transaction/observation';
12
12
  // ─── Server Delta Schema ─────────────────────────────────────────────────────
13
13
  /**
14
14
  * A delta as it arrives in a bootstrap payload.
@@ -5,4 +5,4 @@
5
5
  * the transport object. This path re-exports it so existing importers stay
6
6
  * unchanged.
7
7
  */
8
- export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingClaim, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/wsFrameHandlers';
8
+ export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingClaim, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/websocket';
@@ -5,4 +5,4 @@
5
5
  * the transport object. This path re-exports it so existing importers stay
6
6
  * unchanged.
7
7
  */
8
- export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, } from '@abloatai/transaction/transport/wsFrameHandlers';
8
+ export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, } from '@abloatai/transaction/transport/websocket';
@@ -0,0 +1,41 @@
1
+ import type { Model } from './Model.js';
2
+ import type { BootstrapData } from './sync/BootstrapFetcher.js';
3
+ import type { QueuedMutation } from './transactions/mutations/MutationQueue.js';
4
+ import type { CommitTransaction } from './transactions/mutations/commitLane.js';
5
+ export interface SyncObserver {
6
+ onSync?: (event: SyncEvent) => void;
7
+ }
8
+ export interface SyncEvent {
9
+ type: 'create' | 'update' | 'delete' | 'archive' | 'rollback';
10
+ modelType: string;
11
+ model?: Model;
12
+ modelId?: string;
13
+ transactionType?: string;
14
+ }
15
+ export interface SyncState {
16
+ connectionState: 'connected' | 'disconnected' | 'connecting';
17
+ pendingMutations: number;
18
+ lastSyncAt?: Date;
19
+ error?: Error;
20
+ }
21
+ export interface RehydrationStats {
22
+ added: number;
23
+ updated: number;
24
+ removed: number;
25
+ skipped: number;
26
+ healed: number;
27
+ elapsedMs: number;
28
+ }
29
+ export type EventHandler = () => void;
30
+ /** The bootstrap fields applied to the local object pool. */
31
+ export type BootstrapSnapshot = Pick<BootstrapData, 'models' | 'failedModels'> & Partial<Pick<BootstrapData, 'lastSyncId'>>;
32
+ /** A completed queued mutation or explicit commit. */
33
+ export type CompletedTransaction = (Pick<QueuedMutation, 'id' | 'modelId' | 'syncIdNeededForCompletion'> & {
34
+ lastSyncId?: undefined;
35
+ operations?: undefined;
36
+ }) | (Pick<CommitTransaction, 'id' | 'lastSyncId' | 'operations'> & {
37
+ modelId?: undefined;
38
+ syncIdNeededForCompletion?: undefined;
39
+ });
40
+ /** Normalize an untyped server timestamp for last-write-wins comparison. */
41
+ export declare function toEpochMs(value: unknown): number;
@@ -0,0 +1,11 @@
1
+ /** Normalize an untyped server timestamp for last-write-wins comparison. */
2
+ export function toEpochMs(value) {
3
+ if (!value)
4
+ return 0;
5
+ if (value instanceof Date)
6
+ return value.getTime();
7
+ if (typeof value === 'string' || typeof value === 'number') {
8
+ return new Date(value).getTime();
9
+ }
10
+ return 0;
11
+ }
@@ -1,5 +1,5 @@
1
- import type { DurableWriteStore } from '@abloatai/transaction/durableWrites';
2
- import type { PendingWrite } from '@abloatai/transaction/transactions/confirmation/pendingWrite';
1
+ import type { DurableWriteStore } from '@abloatai/transaction/commit';
2
+ import type { PendingWrite } from '@abloatai/transaction/commit';
3
3
  export interface CommitOutboxDatabase {
4
4
  sealTransactionRecord(record: PendingWrite, consumedRecordIds: readonly string[]): Promise<unknown>;
5
5
  getPersistedTransactions(): Promise<readonly unknown[]>;
@@ -17,9 +17,10 @@ import type { MutationPersistencePort } from '../../mutationPersistence.js';
17
17
  import type { RuntimeContext } from '../../RuntimeContext.js';
18
18
  import { type LogPositionPort } from '../../logPosition.js';
19
19
  import type { WriteOptions } from '../../interfaces/index.js';
20
- import type { StaleNotification, ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
20
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
21
+ import { type CommitOperationResult } from '@abloatai/transaction/commit';
21
22
  import { type MutationInput, type QueuedMutation, type UserContext } from './commitPayload.js';
22
- import { type CommitOutboxScope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
23
+ import { type CommitOutboxScope } from '@abloatai/transaction/commit';
23
24
  import type { DurableWriteStore } from './durableWriteStore.js';
24
25
  import { type LocalMutationPort } from './localMutation.js';
25
26
  import { type CommitTransaction } from './commitLane.js';
@@ -150,7 +151,7 @@ export declare class MutationQueue extends EventEmitter {
150
151
  private get queueCoalescingContext();
151
152
  private get batchProcessingContext();
152
153
  private get failureHandlingContext();
153
- private get conflictPolicyContext();
154
+ private get conflictResolutionContext();
154
155
  private get processingSchedulerContext();
155
156
  private get pendingDrainContext();
156
157
  private get durableCommitRestoreContext();
@@ -162,18 +163,6 @@ export declare class MutationQueue extends EventEmitter {
162
163
  private computePriorityScore;
163
164
  private ensureDerivedFields;
164
165
  private entityKey;
165
- /** Collision-safe receipt target identity across models sharing a row id. */
166
- private receiptTargetKey;
167
- /**
168
- * Relates stale notifications back to write targets without assuming the
169
- * server's canonical model name uses the same spelling as the public schema
170
- * key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
171
- * is the compatibility fallback. An ambiguous same-id cross-model mismatch
172
- * is deliberately left unclassified, so it cannot falsely settle a queued
173
- * write. A notification with no write-target id (or an explicit group) is a
174
- * declared-read conflict and holds the whole batch.
175
- */
176
- private classifyReceiptNotifications;
177
166
  private resolveConfirmation;
178
167
  private takeUnsentCreateForModel;
179
168
  private cancelUnsentCreateForDelete;
@@ -184,7 +173,6 @@ export declare class MutationQueue extends EventEmitter {
184
173
  private config;
185
174
  private executingCount;
186
175
  private readonly localMutationPort;
187
- private commitNotifications;
188
176
  /** Zero-row targets returned on a successful atomic commit receipt. */
189
177
  private commitMissingIds;
190
178
  private readonly deltaConfirmation;
@@ -366,13 +354,12 @@ export declare class MutationQueue extends EventEmitter {
366
354
  /** Enqueues a pre-built atomic commit through the commit API coordinator. */
367
355
  enqueueCommit(clientTxId: string, operations: CommitTransaction['operations'], options?: {
368
356
  reads?: ReadDependency[] | null;
369
- track?: TrackDependency[] | null;
370
357
  }): Promise<void>;
371
358
  private processCommitLane;
372
359
  waitForCommitReceipt(clientTxId: string): Promise<{
373
360
  lastSyncId: number;
374
- notifications?: StaleNotification[];
375
361
  missingIds?: string[];
362
+ operationResults?: CommitOperationResult[];
376
363
  }>;
377
364
  private isReorderPayload;
378
365
  private isPermanentError;