@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
@@ -18,44 +18,39 @@ import type { LocalModel } from '../../localModelContract.js';
18
18
  import type { MutationPersistencePort } from '../../mutationPersistence.js';
19
19
  import { globalRuntime } from '../../context.js';
20
20
  import type { RuntimeContext } from '../../RuntimeContext.js';
21
- import type { MutationOperationType } from '@abloatai/transaction/types';
22
21
  import {
23
- AbloError,
24
22
  AbloConnectionError,
25
23
  AbloIdempotencyError,
26
- AbloNotFoundError,
27
- AbloValidationError,
28
- errorCodeSpec,
29
24
  } from '@abloatai/transaction/errors';
30
25
  import {
31
26
  LogPosition,
32
27
  type LogPositionPort,
33
28
  } from '../../logPosition.js';
34
29
  import type { WriteOptions } from '../../interfaces/index.js';
35
- import type { OnStaleMode, StaleNotification, ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
30
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
36
31
  import {
37
- mutationCommitResultSchema,
32
+ type CommitOperationResult,
38
33
  type MutationCommitResult,
39
- } from '@abloatai/transaction/wire/commit';
34
+ } from '@abloatai/transaction/commit';
40
35
  import {
41
- projectCommitPayload,
42
36
  computePriorityScore,
43
37
  normalizeModelKey,
44
38
  // Includes stale guards as well as request identity/audit barriers.
45
- hasCommitCoalescingBarrier,
46
- applyWriteOptions,
47
- asTransportError,
48
- extractStatusCode,
49
- TX_TYPE_TO_MUTATION_OP,
50
39
  type MutationInput,
51
40
  type QueuedMutation,
52
41
  type UserContext,
53
- type WriteOperationFields,
54
42
  } from './commitPayload.js';
43
+ import {
44
+ generateTransactionId,
45
+ mergeMutationData,
46
+ createDataFor,
47
+ changesToInput,
48
+ updateDataFor,
49
+ previousDataFor,
50
+ } from './mutationInput.js';
55
51
  import { MutationStore } from './MutationStore.js';
56
52
  import {
57
53
  entityKey,
58
- mergeUpdateData,
59
54
  takeUnsentCreateForModel,
60
55
  findCreateBarrierForDelete,
61
56
  deferDeleteUntilCreateSettles,
@@ -66,9 +61,6 @@ import {
66
61
  deserializePersistedTransaction,
67
62
  isNonReplayablePersistedRow,
68
63
  pendingMutationRecordId,
69
- legacyPendingMutationRecordSchema,
70
- pendingMutationRecordSchema,
71
- persistedMutationSchema,
72
64
  } from './replayValidation.js';
73
65
  import {
74
66
  deserializeLegacyPendingMutation,
@@ -80,16 +72,10 @@ import {
80
72
  } from './mutationPersistence.js';
81
73
  import {
82
74
  createCommitEnvelopeMember,
83
- createDurableCommitEnvelope,
84
- commitEnvelopeRecordId,
85
- durableCommitEnvelopeSchema,
86
75
  type DurableCommitEnvelope,
87
- type DurableCommitOperation,
88
- type DurableCommitOperationInput,
89
76
  type CommitOutboxScope,
90
- } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
77
+ } from '@abloatai/transaction/commit';
91
78
  import type { DurableWriteStore } from './durableWriteStore.js';
92
- import { stableStringify } from '@abloatai/transaction/utils/json';
93
79
  import {
94
80
  createLocalMutationPort,
95
81
  type LocalMutationPort,
@@ -101,7 +87,6 @@ import {
101
87
  removeDurableCommit,
102
88
  sealDurableCommit,
103
89
  type CommitTransportContext,
104
- type SealDurableCommitInput,
105
90
  } from './commitTransport.js';
106
91
  import {
107
92
  processCommitLane,
@@ -122,7 +107,7 @@ import {
122
107
  import { enqueueTransaction, type QueueCoalescingContext } from './queueCoalescing.js';
123
108
  import { processBatch, type BatchProcessingContext } from './batchProcessing.js';
124
109
  import { handleFailure, type FailureHandlingContext } from './failureHandling.js';
125
- import { handleConflict as resolveConflict, isPermanentError as classifyPermanentError, isDefinitiveRejection as classifyDefinitiveRejection, type ConflictPolicyContext } from './failurePolicy.js';
110
+ import { handleConflict as resolveConflict, isPermanentError as classifyPermanentError, isDefinitiveRejection as classifyDefinitiveRejection, type ConflictResolutionContext } from './failurePolicy.js';
126
111
  import { takeNextExecutionBatch as selectExecutionBatch, takePendingDrainBatch as selectPendingDrainBatch } from './executionSelection.js';
127
112
  import { scheduleProcessing as scheduleProcessingExternal, type ProcessingSchedulerContext } from './processingScheduler.js';
128
113
  import {
@@ -245,18 +230,18 @@ export class MutationQueue extends EventEmitter {
245
230
  private readonly runtime: RuntimeContext;
246
231
  /** Durable transaction journal owned by this queue, before commit sealing. */
247
232
  private persistence: MutationPersistencePort | null = null;
248
- private deferredMutations: Array<{
233
+ private deferredMutations: {
249
234
  type: 'create' | 'update' | 'delete' | 'archive';
250
235
  model: LocalModel;
251
236
  capturedChanges?: Record<string, unknown>;
252
237
  writeOptions?: WriteOptions;
253
- }> = [];
254
- private pendingPersistenceStages: Array<{
238
+ }[] = [];
239
+ private pendingPersistenceStages: {
255
240
  transaction: QueuedMutation;
256
241
  modelData: Record<string, unknown>;
257
242
  resolve: () => void;
258
243
  reject: (error: Error) => void;
259
- }> = [];
244
+ }[] = [];
260
245
  private persistenceStageScheduled = false;
261
246
  private pendingDrainPromise: Promise<void> | null = null;
262
247
 
@@ -315,7 +300,7 @@ export class MutationQueue extends EventEmitter {
315
300
  commitOutbox: this.commitOutbox,
316
301
  commitOutboxScope: this.commitOutboxScope,
317
302
  mutationExecutor: this.mutationExecutor,
318
- emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
303
+ emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
319
304
  };
320
305
  }
321
306
 
@@ -328,26 +313,24 @@ export class MutationQueue extends EventEmitter {
328
313
  retryBackoff: this.config.retryBackoff,
329
314
  },
330
315
  commitLane: this.commitLane,
331
- commitNotifications: this.commitNotifications,
332
316
  commitMissingIds: this.commitMissingIds,
333
317
  commitProcessing: this.commitProcessing,
334
318
  setCommitProcessing: (value) => { this.commitProcessing = value; },
335
319
  durableReplayBlock: this.durableReplayBlock,
336
320
  sealDurableCommit: (input) => this.sealDurableCommit(input),
337
- assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
321
+ assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
338
322
  dispatchCommit: async (envelope) => this.parseMutationCommitResult(
339
323
  await this.dispatchCommitBounded(envelope.operations, {
340
324
  idempotencyKey: envelope.idempotencyKey,
341
325
  ...(envelope.commitOptions.reads ? { reads: envelope.commitOptions.reads } : {}),
342
- ...(envelope.commitOptions.track ? { track: envelope.commitOptions.track } : {}),
343
326
  }),
344
327
  ),
345
328
  persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
346
329
  removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
347
330
  queuedCommitEchoSyncId: (transaction) => this.queuedCommitEchoSyncId(transaction),
348
- completeQueuedCommit: (transaction, syncId) => this.completeQueuedCommit(transaction, syncId),
349
- scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId),
350
- noteAck: (syncId) => this.noteAck(syncId),
331
+ completeQueuedCommit: (transaction, syncId) => { this.completeQueuedCommit(transaction, syncId); },
332
+ scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
333
+ noteAck: (syncId) => { this.noteAck(syncId); },
351
334
  isDefinitiveRejection: (error) => this.isDefinitiveRejection(error),
352
335
  isPermanentError: (error) => this.isPermanentError(error),
353
336
  scheduleRetry: (delayMs) => {
@@ -357,14 +340,13 @@ export class MutationQueue extends EventEmitter {
357
340
  void this.processCommitLane();
358
341
  }, delayMs);
359
342
  },
360
- emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
343
+ emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
361
344
  };
362
345
  }
363
346
 
364
347
  private get commitReceiptContext(): CommitReceiptContext {
365
348
  return {
366
349
  commitStore: this.commitStore,
367
- commitNotifications: this.commitNotifications,
368
350
  commitMissingIds: this.commitMissingIds,
369
351
  replicationLagErrors: this.replicationLagErrors,
370
352
  on: (event, listener) => this.on(event, listener),
@@ -374,15 +356,15 @@ export class MutationQueue extends EventEmitter {
374
356
 
375
357
  private get commitApiContext(): CommitApiContext {
376
358
  return {
377
- assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
359
+ assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
378
360
  commitStore: this.commitStore,
379
361
  commitLane: this.commitLane,
380
362
  replicationLagErrors: this.replicationLagErrors,
381
- clearReplicationLagState: (transactionId) => this.clearReplicationLagState(transactionId),
363
+ clearReplicationLagState: (transactionId) => { this.clearReplicationLagState(transactionId); },
382
364
  nextCommitSequence: () => this.nextCommitSequence(),
383
365
  sealDurableCommit: (input) => this.sealDurableCommit(input),
384
366
  processCommitLane: () => this.processCommitLane(),
385
- emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
367
+ emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
386
368
  };
387
369
  }
388
370
 
@@ -390,7 +372,7 @@ export class MutationQueue extends EventEmitter {
390
372
  return {
391
373
  enableOptimistic: this.config.enableOptimistic,
392
374
  persistenceReady: !!this.persistence && !!this.commitOutboxScope,
393
- assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
375
+ assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
394
376
  generateId: () => this.generateId(),
395
377
  normalizeModelKey,
396
378
  computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
@@ -399,11 +381,11 @@ export class MutationQueue extends EventEmitter {
399
381
  extractPreviousData: (model, input) => this.extractPreviousData(model, input),
400
382
  mapChangesToInput: (modelName, changes) => this.mapChangesToInput(modelName, changes),
401
383
  isReorderPayload: (input) => this.isReorderPayload(input),
402
- attachConfirmation: (transaction) => this.attachConfirmation(transaction),
403
- add: (transaction) => this.store.add(transaction),
404
- applyOptimisticCreate: (model, transaction) => this.applyOptimisticCreate(model, transaction),
405
- applyOptimisticUpdate: (model, transaction) => this.applyOptimisticUpdate(model, transaction),
406
- applyOptimisticDelete: (model, transaction) => this.applyOptimisticDelete(model, transaction),
384
+ attachConfirmation: (transaction) => { this.attachConfirmation(transaction); },
385
+ add: (transaction) => { this.store.add(transaction); },
386
+ applyOptimisticCreate: (model, transaction) => { this.applyOptimisticCreate(model, transaction); },
387
+ applyOptimisticUpdate: (model, transaction) => { this.applyOptimisticUpdate(model, transaction); },
388
+ applyOptimisticDelete: (model, transaction) => { this.applyOptimisticDelete(model, transaction); },
407
389
  takeUnsentCreateForModel: (modelName, modelId) => this.takeUnsentCreateForModel(modelName, modelId),
408
390
  cancelUnsentCreateForDelete: (transaction) => this.cancelUnsentCreateForDelete(transaction),
409
391
  completeLocalDelete: (model, context, writeOptions, sourceMutationIds) => this.completeLocalDelete(model, context, writeOptions, sourceMutationIds),
@@ -411,11 +393,11 @@ export class MutationQueue extends EventEmitter {
411
393
  pendingMergeByModel: this.pendingMergeByModel,
412
394
  inFlightByModel: this.inFlightByModel,
413
395
  findCreateBarrierForDelete: (modelName, modelId) => this.findCreateBarrierForDelete(modelName, modelId),
414
- deferDeleteUntilCreateSettles: (create, transaction) => this.deferDeleteUntilCreateSettles(create, transaction),
396
+ deferDeleteUntilCreateSettles: (create, transaction) => { this.deferDeleteUntilCreateSettles(create, transaction); },
415
397
  logger: this.runtime.logger,
416
398
  persistAndStage: (transaction, modelData) => this.persistAndStage(transaction, modelData),
417
399
  persistQueuedTransaction: (transaction, modelData) => this.persistQueuedTransaction(transaction, modelData),
418
- stageTransaction: (transaction) => this.stageTransaction(transaction),
400
+ stageTransaction: (transaction) => { this.stageTransaction(transaction); },
419
401
  emit: (event, payload) => this.emit(event, payload),
420
402
  };
421
403
  }
@@ -425,9 +407,9 @@ export class MutationQueue extends EventEmitter {
425
407
  executionQueue: this.executionQueue,
426
408
  inFlightByModel: this.inFlightByModel,
427
409
  pendingMergeByModel: this.pendingMergeByModel,
428
- ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
429
- scheduleProcessing: (immediate) => this.scheduleProcessing(immediate),
430
- storeRemove: (transactionId) => this.store.remove(transactionId),
410
+ ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
411
+ scheduleProcessing: (immediate) => { this.scheduleProcessing(immediate); },
412
+ storeRemove: (transactionId) => { this.store.remove(transactionId); },
431
413
  };
432
414
  }
433
415
 
@@ -440,7 +422,7 @@ export class MutationQueue extends EventEmitter {
440
422
  isProcessing: this.isProcessing,
441
423
  setIsProcessing: (value) => { this.isProcessing = value; },
442
424
  takeNextExecutionBatch: () => this.takeNextExecutionBatch(),
443
- ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
425
+ ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
444
426
  ensureCommitEnvelope: (batch) => this.ensureCommitEnvelope([...batch]),
445
427
  executingCount: this.executingCount,
446
428
  setExecutingCount: (value) => { this.executingCount = value; },
@@ -449,31 +431,28 @@ export class MutationQueue extends EventEmitter {
449
431
  generateId: () => this.generateId(),
450
432
  computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
451
433
  store: this.store,
452
- enqueue: (transaction) => this.enqueue(transaction),
434
+ enqueue: (transaction) => { this.enqueue(transaction); },
453
435
  optimisticUpdates: this.localMutationPort.updates,
454
- commitNotifications: this.commitNotifications,
455
436
  commitMissingIds: this.commitMissingIds,
456
437
  sourceMutationIdsFor: (batch) => this.sourceMutationIdsFor(batch),
457
438
  dispatchCommitBounded: (...args) => this.dispatchCommitBounded(...args),
458
439
  parseMutationCommitResult: (value) => this.parseMutationCommitResult(value),
459
440
  persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
460
441
  removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
461
- assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
442
+ assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
462
443
  sealDurableCommit: (input) => this.sealDurableCommit(input),
463
- noteAck: (syncId) => this.noteAck(syncId),
464
- classifyReceiptNotifications: (operations, notifications) => this.classifyReceiptNotifications(operations, notifications),
465
- receiptTargetKey: (modelName, modelId) => this.receiptTargetKey(modelName, modelId),
466
- scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId),
467
- scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs),
468
- clearReplicationLagState: (transactionId) => this.clearReplicationLagState(transactionId),
469
- completeQueuedCommit: (transaction, syncId) => this.completeQueuedCommit(transaction, syncId),
444
+ noteAck: (syncId) => { this.noteAck(syncId); },
445
+ scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
446
+ scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => { this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs); },
447
+ clearReplicationLagState: (transactionId) => { this.clearReplicationLagState(transactionId); },
448
+ completeQueuedCommit: (transaction, syncId) => { this.completeQueuedCommit(transaction, syncId); },
470
449
  queuedCommitMatchesCorrelation: (transaction, correlationId) => this.queuedCommitMatchesCorrelation(transaction, correlationId),
471
450
  recentDeltaCorrelations: this.recentDeltaCorrelations,
472
451
  lastSeenSyncId: this.lastSeenSyncId,
473
- scheduleProcessing: (immediate) => this.scheduleProcessing(immediate),
452
+ scheduleProcessing: (immediate) => { this.scheduleProcessing(immediate); },
474
453
  handleFailure: (transaction, error) => this.handleFailure(transaction, error),
475
454
  isDefinitiveRejection: (error) => this.isDefinitiveRejection(error),
476
- emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
455
+ emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
477
456
  emit: (event, payload) => this.emit(event, payload),
478
457
  rollbackOptimistic: (transaction, reason, error) => this.rollbackOptimistic(transaction, reason, error),
479
458
  };
@@ -486,20 +465,20 @@ export class MutationQueue extends EventEmitter {
486
465
  store: this.store,
487
466
  isPermanentError: (error) => this.isPermanentError(error),
488
467
  rollbackOptimistic: (transaction, reason, error) => this.rollbackOptimistic(transaction, reason, error),
489
- enqueue: (transaction) => this.enqueue(transaction),
468
+ enqueue: (transaction) => { this.enqueue(transaction); },
490
469
  getLastPermanentErrorSignature: () => this.lastPermanentErrorSig,
491
470
  setLastPermanentErrorSignature: (signature) => { this.lastPermanentErrorSig = signature; },
492
471
  emit: (event, payload) => this.emit(event, payload),
493
472
  };
494
473
  }
495
474
 
496
- private get conflictPolicyContext(): ConflictPolicyContext {
475
+ private get conflictResolutionContext(): ConflictResolutionContext {
497
476
  return {
498
477
  config: this.config,
499
478
  store: this.store,
500
479
  rollbackOptimistic: (transaction, reason) => this.rollbackOptimistic(transaction, reason),
501
480
  mergeData: (local, remote) => this.mergeData(local, remote),
502
- enqueue: (transaction) => this.enqueue(transaction),
481
+ enqueue: (transaction) => { this.enqueue(transaction); },
503
482
  };
504
483
  }
505
484
 
@@ -524,21 +503,21 @@ export class MutationQueue extends EventEmitter {
524
503
  store: this.store,
525
504
  executionQueue: this.executionQueue,
526
505
  optimisticUpdates: this.localMutationPort.updates,
527
- assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
506
+ assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
528
507
  processCommitLane: () => this.processCommitLane(),
529
508
  takePendingDrainBatch: (pending) => this.takePendingDrainBatch(pending),
530
509
  ensureCommitEnvelope: (batch) => this.ensureCommitEnvelope(batch),
531
- ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
510
+ ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
532
511
  sourceMutationIdsFor: (batch) => this.sourceMutationIdsFor(batch),
533
512
  sealDurableCommit: (input) => this.sealDurableCommit(input),
534
- assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
513
+ assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
535
514
  parseMutationCommitResult: (value) => this.parseMutationCommitResult(value),
536
515
  dispatchCommitBounded: (...args) => this.dispatchCommitBounded(...args),
537
516
  persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
538
517
  removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
539
- scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId),
540
- scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs),
541
- enqueue: (transaction) => this.enqueue(transaction),
518
+ scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
519
+ scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => { this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs); },
520
+ enqueue: (transaction) => { this.enqueue(transaction); },
542
521
  recentDeltaCorrelations: this.recentDeltaCorrelations,
543
522
  emit: (event, payload) => this.emit(event, payload),
544
523
  };
@@ -564,7 +543,7 @@ export class MutationQueue extends EventEmitter {
564
543
  commitOutboxScope: this.commitOutboxScope,
565
544
  config: this.config,
566
545
  store: this.store,
567
- enqueue: (transaction) => this.enqueue(transaction),
546
+ enqueue: (transaction) => { this.enqueue(transaction); },
568
547
  computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
569
548
  deserializeTransaction: (data) => this.deserializeTransaction(data),
570
549
  };
@@ -637,80 +616,7 @@ export class MutationQueue extends EventEmitter {
637
616
  return entityKey(modelName, modelId);
638
617
  }
639
618
 
640
- /** Collision-safe receipt target identity across models sharing a row id. */
641
- private receiptTargetKey(modelName: string, modelId: string): string {
642
- return stableStringify([normalizeModelKey(modelName), modelId]);
643
- }
644
-
645
- /**
646
- * Relates stale notifications back to write targets without assuming the
647
- * server's canonical model name uses the same spelling as the public schema
648
- * key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
649
- * is the compatibility fallback. An ambiguous same-id cross-model mismatch
650
- * is deliberately left unclassified, so it cannot falsely settle a queued
651
- * write. A notification with no write-target id (or an explicit group) is a
652
- * declared-read conflict and holds the whole batch.
653
- */
654
- private classifyReceiptNotifications(
655
- operations: readonly { model: string; id: string }[],
656
- notifications: readonly StaleNotification[],
657
- ): {
658
- holdsEntireBatch: boolean;
659
- heldTargets: Set<string>;
660
- notificationsByTarget: Map<string, StaleNotification[]>;
661
- } {
662
- const targets = operations.map((operation) => ({
663
- id: operation.id,
664
- key: this.receiptTargetKey(operation.model, operation.id),
665
- }));
666
- const heldTargets = new Set<string>();
667
- const notificationsByTarget = new Map<string, StaleNotification[]>();
668
- let holdsEntireBatch = false;
669
-
670
- for (const notification of notifications) {
671
- // Scope is decided before any target matching. A group premise fires over
672
- // the WHOLE batch by convention, and its `target` now names the row that
673
- // actually moved — which may well be a row this batch is writing, so
674
- // matching first would misread a batch-wide hold as a per-row one.
675
- if (notification.scope === 'group') {
676
- holdsEntireBatch = true;
677
- continue;
678
- }
679
- const candidates = targets.filter(
680
- (target) => target.id === notification.target.id,
681
- );
682
- const notificationKey = this.receiptTargetKey(
683
- notification.target.model,
684
- notification.target.id,
685
- );
686
- const exactTargets = candidates.filter(
687
- (target) => target.key === notificationKey,
688
- );
689
- const candidateKeys = new Set(
690
- (exactTargets.length > 0 ? exactTargets : candidates).map(
691
- (target) => target.key,
692
- ),
693
- );
694
-
695
- if (candidates.length === 0) {
696
- holdsEntireBatch = true;
697
- continue;
698
- }
699
- if (candidateKeys.size !== 1) {
700
- // Same id across multiple differently named models with no exact match:
701
- // the id-only compatibility fallback is ambiguous. Await the echo.
702
- continue;
703
- }
704
- const [targetKey] = candidateKeys;
705
- if (!targetKey) continue;
706
- heldTargets.add(targetKey);
707
- const targetNotifications = notificationsByTarget.get(targetKey) ?? [];
708
- targetNotifications.push(notification);
709
- notificationsByTarget.set(targetKey, targetNotifications);
710
- }
711
-
712
- return { holdsEntireBatch, heldTargets, notificationsByTarget };
713
- }
619
+ /* Stale receipt classification was removed; stale premises now reject. */
714
620
 
715
621
  private resolveConfirmation(transaction: QueuedMutation): void {
716
622
  const resolver = this.confirmationResolvers.get(transaction.id);
@@ -821,11 +727,6 @@ export class MutationQueue extends EventEmitter {
821
727
 
822
728
  private readonly localMutationPort: LocalMutationPort;
823
729
 
824
- // Stale-context notifications, keyed by transaction id. When the server
825
- // accepts a commit but reports that an operation's premise had moved,
826
- // the notification lands here from the commit acknowledgement and is drained
827
- // by `waitForCommitReceipt`, so the receipt can carry it back to the caller.
828
- private commitNotifications = new Map<string, StaleNotification[]>();
829
730
  /** Zero-row targets returned on a successful atomic commit receipt. */
830
731
  private commitMissingIds = new Map<string, string[]>();
831
732
 
@@ -1688,7 +1589,7 @@ export class MutationQueue extends EventEmitter {
1688
1589
  async enqueueCommit(
1689
1590
  clientTxId: string,
1690
1591
  operations: CommitTransaction['operations'],
1691
- options: { reads?: ReadDependency[] | null; track?: TrackDependency[] | null } = {},
1592
+ options: { reads?: ReadDependency[] | null } = {},
1692
1593
  ): Promise<void> {
1693
1594
  return enqueueCommit(this.commitApiContext, clientTxId, operations, options);
1694
1595
  }
@@ -1701,8 +1602,8 @@ export class MutationQueue extends EventEmitter {
1701
1602
  clientTxId: string,
1702
1603
  ): Promise<{
1703
1604
  lastSyncId: number;
1704
- notifications?: StaleNotification[];
1705
1605
  missingIds?: string[];
1606
+ operationResults?: CommitOperationResult[];
1706
1607
  }> {
1707
1608
  return waitForCommitReceipt(this.commitReceiptContext, clientTxId);
1708
1609
  }
@@ -1725,7 +1626,7 @@ export class MutationQueue extends EventEmitter {
1725
1626
  }
1726
1627
 
1727
1628
  async handleConflict(transaction: QueuedMutation, serverData: MutationInput): Promise<void> {
1728
- await resolveConflict(this.conflictPolicyContext, transaction, serverData);
1629
+ await resolveConflict(this.conflictResolutionContext, transaction, serverData);
1729
1630
  }
1730
1631
 
1731
1632
  private applyOptimisticCreate(model: LocalModel, transaction: QueuedMutation): void {
@@ -1879,47 +1780,33 @@ export class MutationQueue extends EventEmitter {
1879
1780
  }
1880
1781
 
1881
1782
  /** Generates a unique local transaction id. */
1783
+ // The payload rules live in `mutationInput`; these keep the call sites here
1784
+ // reading as the queue's own vocabulary.
1882
1785
  private generateId(): string {
1883
- return `tx_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
1786
+ return generateTransactionId();
1884
1787
  }
1885
1788
 
1886
1789
  private mergeData(
1887
1790
  local: MutationInput | undefined,
1888
1791
  remote: MutationInput | undefined
1889
1792
  ): MutationInput {
1890
- return { ...(remote || {}), ...(local || {}) };
1793
+ return mergeMutationData(local, remote);
1891
1794
  }
1892
1795
 
1893
1796
  private extractCreateData(model: LocalModel): MutationInput {
1894
- return projectCommitPayload(model.getModelName(), model.toJSON(), { dropUndefined: false }, this.runtime);
1797
+ return createDataFor(model, this.runtime);
1895
1798
  }
1896
1799
 
1897
1800
  private mapChangesToInput(modelName: string, changes: Record<string, unknown>): MutationInput {
1898
- return projectCommitPayload(modelName, changes, { dropUndefined: true }, this.runtime);
1801
+ return changesToInput(modelName, changes, this.runtime);
1899
1802
  }
1900
1803
 
1901
1804
  private extractUpdateData(model: LocalModel): MutationInput {
1902
- return projectCommitPayload(model.getModelName(), model.getChanges(), { dropUndefined: true }, this.runtime);
1805
+ return updateDataFor(model, this.runtime);
1903
1806
  }
1904
1807
 
1905
- // Derive previous values for changed fields to support accurate rollback.
1906
- // Model-specific special cases do not belong here; a model that needs to
1907
- // surface previous state beyond `modifiedProperties` should expose a typed
1908
- // `getPreviousData()` accessor for this method to call.
1909
1808
  private extractPreviousData(model: LocalModel, updateInput?: MutationInput): MutationInput {
1910
- // When the update's written keys are known, capture a before-image for
1911
- // exactly those keys, so the recorded undo inverse reverts them and nothing
1912
- // else — a full-row inverse would clobber concurrent edits to unrelated
1913
- // fields. `fallbackToLive: false` makes `Model.capturePreviousValues` omit
1914
- // any key it cannot resolve, and `buildUndoOps` then drops an un-revertible
1915
- // inverse rather than inventing one. With no `updateInput` (a full extract)
1916
- // it falls back to every tracked field. `Model.capturePreviousValues` is the
1917
- // single before-image source, shared with
1918
- // `RecordingMutation.snapshotFields`.
1919
- const keys = updateInput
1920
- ? Object.keys(updateInput)
1921
- : [...(model.modifiedProperties instanceof Map ? model.modifiedProperties.keys() : [])];
1922
- return { id: model.id, ...model.capturePreviousValues(keys, { fallbackToLive: false }) };
1809
+ return previousDataFor(model, updateInput);
1923
1810
  }
1924
1811
 
1925
1812
  /** Returns a snapshot of queue counts and the current configuration. */
@@ -2040,7 +1927,6 @@ export class MutationQueue extends EventEmitter {
2040
1927
  this.recentDeltaCorrelations.clear();
2041
1928
  this.commitLane = [];
2042
1929
  this.commitStore.clear();
2043
- this.commitNotifications.clear();
2044
1930
  this.commitMissingIds.clear();
2045
1931
 
2046
1932
  // Clear event listeners
@@ -3,10 +3,9 @@ import type { MutationQueueConfig } from './MutationQueue.js';
3
3
  import type { MutationInput, QueuedMutation } from './commitPayload.js';
4
4
  import type { MutationStore } from './MutationStore.js';
5
5
  import type { OptimisticUpdateEntry } from './localMutation.js';
6
- import type { StaleNotification } from '@abloatai/transaction/coordination/schema';
7
6
  import type { DeltaConfirmationTracker } from './deltaConfirmation.js';
8
- import type { MutationCommitResult } from '@abloatai/transaction/wire/commit';
9
- import type { DurableCommitEnvelope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
7
+ import type { MutationCommitResult } from '@abloatai/transaction/commit';
8
+ import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
10
9
  import { AbloError, AbloNotFoundError } from '@abloatai/transaction/errors';
11
10
  import { applyWriteOptions, normalizeModelKey, TX_TYPE_TO_MUTATION_OP, type WriteOperationFields } from './commitPayload.js';
12
11
  import type { MutationOperationType } from '@abloatai/transaction/types';
@@ -30,7 +29,6 @@ export interface BatchProcessingContext {
30
29
  readonly store: MutationStore;
31
30
  readonly enqueue: (transaction: QueuedMutation) => void;
32
31
  readonly optimisticUpdates: Map<string, OptimisticUpdateEntry>;
33
- readonly commitNotifications: Map<string, StaleNotification[]>;
34
32
  readonly commitMissingIds: Map<string, string[]>;
35
33
  readonly sourceMutationIdsFor: (batch: readonly QueuedMutation[]) => string[];
36
34
  readonly dispatchCommitBounded: (...args: Parameters<import('../../interfaces/index.js').MutationExecutor['commit']>) => ReturnType<import('../../interfaces/index.js').MutationExecutor['commit']>;
@@ -40,11 +38,6 @@ export interface BatchProcessingContext {
40
38
  readonly assertEnvelopeInsideReplayWindow: (envelope: DurableCommitEnvelope) => void;
41
39
  readonly sealDurableCommit: (input: Parameters<typeof import('./commitTransport.js').sealDurableCommit>[1]) => Promise<DurableCommitEnvelope>;
42
40
  readonly noteAck: (syncId: number | undefined) => void;
43
- readonly classifyReceiptNotifications: (
44
- operations: readonly { model: string; id: string }[],
45
- notifications: readonly StaleNotification[],
46
- ) => { holdsEntireBatch: boolean; heldTargets: Set<string>; notificationsByTarget: Map<string, StaleNotification[]> };
47
- readonly receiptTargetKey: (modelName: string, modelId: string) => string;
48
41
  readonly scheduleReplicationLagTimeout: (transactionId: string, clientTxId?: string, correlationId?: string) => void;
49
42
  readonly scheduleDeltaConfirmationTimeout: (transaction: QueuedMutation, timeoutMs: number) => void;
50
43
  readonly clearReplicationLagState: (transactionId: string) => void;
@@ -170,24 +163,6 @@ export async function processBatch(ctx: BatchProcessingContext): Promise<void> {
170
163
  );
171
164
  const lastSyncId = result.lastSyncId;
172
165
 
173
- const notifications = result.notifications;
174
- const {
175
- holdsEntireBatch,
176
- heldTargets,
177
- notificationsByTarget,
178
- } = ctx.classifyReceiptNotifications(
179
- batchOps.map(({ op }) => ({ model: op.model, id: op.id })),
180
- notifications ?? [],
181
- );
182
- for (const { tx } of batchOps) {
183
- const txTarget = ctx.receiptTargetKey(tx.modelKey, tx.modelId);
184
- const txNotifs = holdsEntireBatch
185
- ? notifications
186
- : notificationsByTarget.get(txTarget);
187
- if (txNotifs && txNotifs.length > 0) {
188
- ctx.commitNotifications.set(tx.id, txNotifs);
189
- }
190
- }
191
166
  const missingIds = new Set(result.missingIds ?? []);
192
167
  const settlingBatchOps: typeof batchOps = [];
193
168
  for (const entry of batchOps) {
@@ -202,17 +177,6 @@ export async function processBatch(ctx: BatchProcessingContext): Promise<void> {
202
177
  );
203
178
  continue;
204
179
  }
205
- if (
206
- holdsEntireBatch ||
207
- heldTargets.has(ctx.receiptTargetKey(tx.modelKey, tx.modelId))
208
- ) {
209
- await ctx.rollbackOptimistic(tx, 'conflict_server_wins');
210
- ctx.store.updateStatus(tx.id, 'completed');
211
- ctx.emit('transaction:completed', tx);
212
- ctx.emit(`transaction:completed:${tx.id}`, tx);
213
- ctx.optimisticUpdates.delete(tx.id);
214
- continue;
215
- }
216
180
  settlingBatchOps.push(entry);
217
181
  }
218
182