@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
@@ -1,7 +1,7 @@
1
1
  import { AbloIdempotencyError } from '@abloatai/transaction/errors';
2
- import type { ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
2
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
3
3
  import { stableStringify } from '@abloatai/transaction/utils/json';
4
- import type { DurableCommitEnvelope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
4
+ import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
5
5
  import type { CommitTransaction } from './commitLane.js';
6
6
  import type { SealDurableCommitInput } from './commitTransport.js';
7
7
 
@@ -21,7 +21,7 @@ export async function enqueueCommit(
21
21
  ctx: CommitApiContext,
22
22
  clientTxId: string,
23
23
  operations: CommitTransaction['operations'],
24
- options: { reads?: ReadDependency[] | null; track?: TrackDependency[] | null } = {},
24
+ options: { reads?: ReadDependency[] | null } = {},
25
25
  ): Promise<void> {
26
26
  ctx.assertDurableReplayOpen();
27
27
  const existing = ctx.commitStore.get(clientTxId);
@@ -30,12 +30,10 @@ export async function enqueueCommit(
30
30
  const existingIntent = stableStringify({
31
31
  operations: existing.operations,
32
32
  reads: existing.reads ?? null,
33
- track: existing.track ?? null,
34
33
  });
35
34
  const incomingIntent = stableStringify({
36
35
  operations,
37
36
  reads: options.reads ?? null,
38
- track: options.track ?? null,
39
37
  });
40
38
  if (existingIntent !== incomingIntent) {
41
39
  throw new AbloIdempotencyError(
@@ -59,7 +57,6 @@ export async function enqueueCommit(
59
57
  kind: 'commit',
60
58
  operations: [...operations],
61
59
  ...(options.reads ? { reads: options.reads } : {}),
62
- ...(options.track ? { track: options.track } : {}),
63
60
  status: 'pending',
64
61
  createdAt: now,
65
62
  attempts: 0,
@@ -73,7 +70,6 @@ export async function enqueueCommit(
73
70
  operations: tx.operations,
74
71
  commitOptions: {
75
72
  ...(tx.reads ? { reads: tx.reads } : {}),
76
- ...(tx.track ? { track: tx.track } : {}),
77
73
  },
78
74
  createdAt: tx.createdAt,
79
75
  sealedAt: tx.sealedAt,
@@ -1,10 +1,13 @@
1
1
  import type { RuntimeContext } from '../../RuntimeContext.js';
2
- import type { ReadDependency, TrackDependency, OnStaleMode, StaleNotification } from '@abloatai/transaction/coordination/schema';
3
- import type { MutationCommitResult } from '@abloatai/transaction/wire/commit';
2
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
3
+ import type {
4
+ CommitOperationResult,
5
+ MutationCommitResult,
6
+ } from '@abloatai/transaction/commit';
4
7
  import type {
5
8
  DurableCommitEnvelope,
6
9
  DurableCommitOperation,
7
- } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
10
+ } from '@abloatai/transaction/commit';
8
11
  import type { SealDurableCommitInput } from './commitTransport.js';
9
12
  import { transientRetryDelayMs } from './failureHandling.js';
10
13
 
@@ -18,16 +21,16 @@ export interface CommitTransaction {
18
21
  input?: Record<string, unknown>;
19
22
  transactionId?: string;
20
23
  readAt?: number | null;
21
- onStale?: OnStaleMode | null;
22
24
  }[];
23
25
  reads?: ReadDependency[] | null;
24
- track?: TrackDependency[] | null;
25
26
  status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed';
26
27
  createdAt: number;
27
28
  attempts: number;
28
29
  transientAttempts?: number;
29
30
  firstTransientFailureAt?: number;
30
31
  lastSyncId?: number;
32
+ /** Fresh transport rows. Deliberately transient: durable server replays redact row data. */
33
+ operationResults?: CommitOperationResult[];
31
34
  correlationId?: string;
32
35
  error?: Error;
33
36
  sealedAt: number;
@@ -45,7 +48,6 @@ export interface CommitLaneContext {
45
48
  retryBackoff: { baseMs: number; capMs: number };
46
49
  };
47
50
  readonly commitLane: CommitTransaction[];
48
- readonly commitNotifications: Map<string, StaleNotification[]>;
49
51
  readonly commitMissingIds: Map<string, string[]>;
50
52
  readonly commitProcessing: boolean;
51
53
  readonly setCommitProcessing: (value: boolean) => void;
@@ -67,7 +69,6 @@ export interface CommitLaneContext {
67
69
 
68
70
  export interface CommitReceiptContext {
69
71
  readonly commitStore: Map<string, CommitTransaction>;
70
- readonly commitNotifications: Map<string, StaleNotification[]>;
71
72
  readonly commitMissingIds: Map<string, string[]>;
72
73
  readonly replicationLagErrors: Map<string, Error>;
73
74
  readonly on: (event: string, listener: (payload: object) => void) => void;
@@ -77,34 +78,38 @@ export interface CommitReceiptContext {
77
78
  export function waitForCommitReceipt(
78
79
  ctx: CommitReceiptContext,
79
80
  clientTxId: string,
80
- ): Promise<{ lastSyncId: number; notifications?: StaleNotification[]; missingIds?: string[] }> {
81
- const drainNotifications = (): StaleNotification[] | undefined => {
82
- const notifications = ctx.commitNotifications.get(clientTxId);
83
- if (!notifications) return undefined;
84
- ctx.commitNotifications.delete(clientTxId);
85
- return notifications.length > 0 ? notifications : undefined;
86
- };
81
+ ): Promise<{
82
+ lastSyncId: number;
83
+ missingIds?: string[];
84
+ operationResults?: CommitOperationResult[];
85
+ }> {
87
86
  const drainMissingIds = (): string[] | undefined => {
88
87
  const ids = ctx.commitMissingIds.get(clientTxId);
89
88
  if (!ids) return undefined;
90
89
  ctx.commitMissingIds.delete(clientTxId);
91
90
  return ids.length > 0 ? ids : undefined;
92
91
  };
93
- const receipt = (lastSyncId: number) => {
92
+ const receipt = (transaction: CommitTransaction, lastSyncId: number) => {
94
93
  const missingIds = drainMissingIds();
95
94
  return {
96
95
  lastSyncId,
97
- notifications: drainNotifications(),
96
+ ...(transaction.operationResults
97
+ ? { operationResults: transaction.operationResults }
98
+ : {}),
98
99
  ...(missingIds ? { missingIds } : {}),
99
100
  };
100
101
  };
101
102
  return new Promise((resolve, reject) => {
102
103
  const existing = ctx.commitStore.get(clientTxId);
103
- if (existing?.status === 'completed') { resolve(receipt(existing.lastSyncId ?? 0)); return; }
104
+ if (existing?.status === 'completed') { resolve(receipt(existing, existing.lastSyncId ?? 0)); return; }
104
105
  if (existing?.status === 'failed' && existing.error) { reject(existing.error); return; }
105
106
  const lagError = ctx.replicationLagErrors.get(clientTxId);
106
107
  if (lagError) { reject(lagError); return; }
107
- const onCompleted = (tx: object) => { cleanup(); resolve(receipt((tx as CommitTransaction).lastSyncId ?? 0)); };
108
+ const onCompleted = (tx: object) => {
109
+ cleanup();
110
+ const completed = tx as CommitTransaction;
111
+ resolve(receipt(completed, completed.lastSyncId ?? 0));
112
+ };
108
113
  const onFailed = (payload: object) => { cleanup(); reject((payload as { error: Error }).error); };
109
114
  const onLagged = (payload: object) => { cleanup(); reject((payload as { error: Error }).error); };
110
115
  const cleanup = () => {
@@ -140,7 +145,6 @@ export async function processCommitLane(ctx: CommitLaneContext): Promise<void> {
140
145
  sourceMutationIds: tx.sourceMutationIds,
141
146
  commitOptions: {
142
147
  ...(tx.reads ? { reads: tx.reads } : {}),
143
- ...(tx.track ? { track: tx.track } : {}),
144
148
  },
145
149
  createdAt: tx.createdAt,
146
150
  sealedAt: tx.sealedAt,
@@ -152,7 +156,7 @@ export async function processCommitLane(ctx: CommitLaneContext): Promise<void> {
152
156
  const result = await ctx.dispatchCommit(durableEnvelope);
153
157
  tx.durableEnvelope = await ctx.persistDurableCommitAcceptance(durableEnvelope, result);
154
158
  tx.lastSyncId = result.lastSyncId;
155
- if (result.notifications?.length) ctx.commitNotifications.set(tx.id, result.notifications);
159
+ if (result.operationResults?.length) tx.operationResults = [...result.operationResults];
156
160
  if (result.missingIds?.length) ctx.commitMissingIds.set(tx.id, result.missingIds);
157
161
  ctx.commitLane.shift();
158
162
  if (result.status === 'queued') {
@@ -13,9 +13,8 @@ import { globalRuntime } from '../../context.js';
13
13
  import type { RuntimeContext } from '../../RuntimeContext.js';
14
14
  import { MutationOperationType } from '@abloatai/transaction/types';
15
15
  import { snapshotJsonValue } from '@abloatai/transaction/utils/json';
16
- import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
17
16
  import type { MutationOptions, WriteOptions } from '../../interfaces/index.js';
18
- import type { CommitEnvelopeMember } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
17
+ import type { CommitEnvelopeMember } from '@abloatai/transaction/commit';
19
18
 
20
19
  export interface UserContext {
21
20
  userId: string;
@@ -198,10 +197,7 @@ export const TX_TYPE_TO_MUTATION_OP: Record<QueuedMutation['type'], MutationOper
198
197
  };
199
198
 
200
199
  export function hasStaleWriteOptions(options?: WriteOptions): boolean {
201
- return (
202
- options?.readAt !== undefined ||
203
- options?.onStale !== undefined
204
- );
200
+ return options?.readAt !== undefined;
205
201
  }
206
202
 
207
203
  /** Options whose identity/audit semantics forbid merging two caller writes. */
@@ -217,7 +213,6 @@ export function hasCommitCoalescingBarrier(options?: WriteOptions): boolean {
217
213
 
218
214
  export interface WriteOperationFields {
219
215
  readAt?: number | null;
220
- onStale?: OnStaleMode | null;
221
216
  fenceToken?: number | null;
222
217
  claimId?: string;
223
218
  options?: Pick<MutationOptions, 'idempotencyKey' | 'label'>;
@@ -225,7 +220,7 @@ export interface WriteOperationFields {
225
220
 
226
221
  /**
227
222
  * Copies a transaction's `writeOptions` onto the wire operation. The
228
- * stale-context guards (`readAt` and `onStale`) sit at the operation's root,
223
+ * stale-context guard (`readAt`) sits at the operation's root,
229
224
  * while `idempotencyKey` and `label` go in its `options` slot — the
230
225
  * `mutation_log` cache key and audit tag. This is the one place caller-supplied
231
226
  * write options cross onto the wire.
@@ -240,9 +235,6 @@ export function applyWriteOptions<T extends object>(
240
235
  if (writeOptions.readAt !== undefined) {
241
236
  operation.readAt = writeOptions.readAt;
242
237
  }
243
- if (writeOptions.onStale !== undefined) {
244
- operation.onStale = writeOptions.onStale;
245
- }
246
238
  if (writeOptions.fenceToken !== undefined) {
247
239
  operation.fenceToken = writeOptions.fenceToken;
248
240
  }
@@ -1,6 +1,6 @@
1
1
  import type { RuntimeContext } from '../../RuntimeContext.js';
2
2
  import type { MutationExecutor } from '../../interfaces/index.js';
3
- import type { ReadDependency, TrackDependency } from '@abloatai/transaction/coordination/schema';
3
+ import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
4
4
  import {
5
5
  AbloConnectionError,
6
6
  AbloError,
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  mutationCommitResultSchema,
11
11
  type MutationCommitResult,
12
- } from '@abloatai/transaction/wire/commit';
12
+ } from '@abloatai/transaction/commit';
13
13
  import {
14
14
  createDurableCommitEnvelope,
15
15
  commitEnvelopeRecordId,
@@ -17,7 +17,7 @@ import {
17
17
  type CommitOutboxScope,
18
18
  type DurableCommitEnvelope,
19
19
  type DurableCommitOperation,
20
- } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
20
+ } from '@abloatai/transaction/commit';
21
21
  import type { DurableWriteStore } from './durableWriteStore.js';
22
22
 
23
23
  export interface CommitTransportContext {
@@ -39,7 +39,6 @@ export interface SealDurableCommitInput {
39
39
  sourceMutationIds?: string[];
40
40
  commitOptions?: {
41
41
  reads?: readonly ReadDependency[] | null;
42
- track?: readonly TrackDependency[] | null;
43
42
  };
44
43
  createdAt: number;
45
44
  sealedAt: number;
@@ -63,9 +62,6 @@ export async function sealDurableCommit(
63
62
  ...(input.commitOptions?.reads !== undefined
64
63
  ? { reads: input.commitOptions.reads === null ? null : [...input.commitOptions.reads] }
65
64
  : {}),
66
- ...(input.commitOptions?.track !== undefined
67
- ? { track: input.commitOptions.track === null ? null : [...input.commitOptions.track] }
68
- : {}),
69
65
  },
70
66
  ...(ctx.commitOutboxScope ? { scope: ctx.commitOutboxScope } : {}),
71
67
  createdAt: input.createdAt,
@@ -1,9 +1,9 @@
1
1
  import { AbloIdempotencyError, AbloValidationError } from '@abloatai/transaction/errors';
2
2
  import type { RuntimeContext } from '../../RuntimeContext.js';
3
3
  import type { CommitTransaction } from './commitLane.js';
4
- import type { DurableCommitEnvelope, CommitOutboxScope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
4
+ import type { DurableCommitEnvelope, CommitOutboxScope } from '@abloatai/transaction/commit';
5
5
  import type { DurableWriteStore } from './durableWriteStore.js';
6
- import { durableCommitEnvelopeSchema } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
6
+ import { durableCommitEnvelopeSchema } from '@abloatai/transaction/commit';
7
7
 
8
8
  export interface DurableCommitRestoreContext {
9
9
  readonly config: { enablePersistence: boolean };
@@ -95,9 +95,6 @@ export async function restoreDurableCommits(ctx: DurableCommitRestoreContext): P
95
95
  ...(envelope.commitOptions.reads
96
96
  ? { reads: [...envelope.commitOptions.reads] }
97
97
  : {}),
98
- ...(envelope.commitOptions.track
99
- ? { track: [...envelope.commitOptions.track] }
100
- : {}),
101
98
  status: 'pending',
102
99
  createdAt: envelope.createdAt,
103
100
  sealedAt: envelope.sealedAt,
@@ -6,16 +6,16 @@
6
6
  *
7
7
  * The port and its config live in the core's `durableWrites` module (a behavior
8
8
  * contract, not a persisted shape); the records that cross it are owned by
9
- * `transactions/confirmation/pendingWrite`.
9
+ * `commit/confirmation/pendingWrite`.
10
10
  */
11
11
 
12
12
  export {
13
13
  durableWriteStoreSchema,
14
14
  durableWritesConfigSchema,
15
- } from '@abloatai/transaction/durableWrites';
15
+ } from '@abloatai/transaction/commit';
16
16
  export type {
17
17
  DurableWriteStore,
18
18
  DurableWritesConfig,
19
- } from '@abloatai/transaction/durableWrites';
20
- export { pendingWriteSchema } from '@abloatai/transaction/transactions/confirmation/pendingWrite';
21
- export type { PendingWrite } from '@abloatai/transaction/transactions/confirmation/pendingWrite';
19
+ } from '@abloatai/transaction/commit';
20
+ export { pendingWriteSchema } from '@abloatai/transaction/commit';
21
+ export type { PendingWrite } from '@abloatai/transaction/commit';
@@ -25,7 +25,7 @@ export function isDefinitiveRejection(error: Error): boolean {
25
25
  return status !== undefined && status >= 400 && status < 500 && status !== 429;
26
26
  }
27
27
 
28
- export interface ConflictPolicyContext {
28
+ export interface ConflictResolutionContext {
29
29
  readonly config: Pick<MutationQueueConfig, 'conflictResolution'>;
30
30
  readonly store: MutationStore;
31
31
  readonly rollbackOptimistic: (transaction: QueuedMutation, reason: string) => Promise<void>;
@@ -34,7 +34,7 @@ export interface ConflictPolicyContext {
34
34
  }
35
35
 
36
36
  export async function handleConflict(
37
- ctx: ConflictPolicyContext,
37
+ ctx: ConflictResolutionContext,
38
38
  transaction: QueuedMutation,
39
39
  serverData: MutationInput,
40
40
  ): Promise<void> {
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The input a queued mutation carries, derived from a local model.
3
+ *
4
+ * Six small rules that answer one question: given a model and what changed on
5
+ * it, what does the commit actually send? They sat as private methods on
6
+ * `MutationQueue` because that is where they were called, which is how a queue
7
+ * ends up also owning the payload vocabulary.
8
+ *
9
+ * The before-image rule is the one worth reading twice, and it is stated in
10
+ * `previousDataFor` rather than here.
11
+ */
12
+
13
+ import type { LocalModel } from '../../localModelContract.js';
14
+ import type { RuntimeContext } from '../../RuntimeContext.js';
15
+ import { projectCommitPayload, type MutationInput } from './commitPayload.js';
16
+
17
+ /** A queued transaction's local identity, unique per process and monotonic enough to sort. */
18
+ export function generateTransactionId(): string {
19
+ return `tx_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
20
+ }
21
+
22
+ /** Local values win: they are the edit the caller made, remote is the base. */
23
+ export function mergeMutationData(
24
+ local: MutationInput | undefined,
25
+ remote: MutationInput | undefined,
26
+ ): MutationInput {
27
+ return { ...(remote ?? {}), ...(local ?? {}) };
28
+ }
29
+
30
+ /** A create sends the whole row, `undefined` included, so absence is explicit. */
31
+ export function createDataFor(model: LocalModel, runtime?: RuntimeContext): MutationInput {
32
+ return projectCommitPayload(model.getModelName(), model.toJSON(), { dropUndefined: false }, runtime);
33
+ }
34
+
35
+ /** An arbitrary change set, projected the way an update is. */
36
+ export function changesToInput(
37
+ modelName: string,
38
+ changes: Record<string, unknown>,
39
+ runtime?: RuntimeContext,
40
+ ): MutationInput {
41
+ return projectCommitPayload(modelName, changes, { dropUndefined: true }, runtime);
42
+ }
43
+
44
+ /** An update sends only what changed. */
45
+ export function updateDataFor(model: LocalModel, runtime?: RuntimeContext): MutationInput {
46
+ return projectCommitPayload(model.getModelName(), model.getChanges(), { dropUndefined: true }, runtime);
47
+ }
48
+
49
+ /**
50
+ * The before-image an undo reverts to.
51
+ *
52
+ * When the update's written keys are known, capture a before-image for exactly
53
+ * those keys, so the recorded undo inverse reverts them and nothing else — a
54
+ * full-row inverse would clobber concurrent edits to unrelated fields.
55
+ * `fallbackToLive: false` makes `Model.capturePreviousValues` omit any key it
56
+ * cannot resolve, and `buildUndoOps` then drops an un-revertible inverse rather
57
+ * than inventing one. With no `updateInput` (a full extract) it falls back to
58
+ * every tracked field.
59
+ *
60
+ * Model-specific special cases do not belong here: a model that needs to
61
+ * surface previous state beyond `modifiedProperties` should expose a typed
62
+ * `getPreviousData()` accessor for this to call.
63
+ */
64
+ export function previousDataFor(model: LocalModel, updateInput?: MutationInput): MutationInput {
65
+ const keys = updateInput
66
+ ? Object.keys(updateInput)
67
+ : [...(model.modifiedProperties instanceof Map ? model.modifiedProperties.keys() : [])];
68
+ return { id: model.id, ...model.capturePreviousValues(keys, { fallbackToLive: false }) };
69
+ }
@@ -1,7 +1,7 @@
1
1
  import type { RuntimeContext } from '../../RuntimeContext.js';
2
2
  import type { MutationPersistencePort } from '../../mutationPersistence.js';
3
3
  import type { MutationQueueConfig } from './MutationQueue.js';
4
- import type { CommitOutboxScope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
4
+ import type { CommitOutboxScope } from '@abloatai/transaction/commit';
5
5
  import type { QueuedMutation } from './commitPayload.js';
6
6
  import { MutationStore } from './MutationStore.js';
7
7
  import { normalizeModelKey } from './commitPayload.js';
@@ -2,8 +2,8 @@ import type { RuntimeContext } from '../../RuntimeContext.js';
2
2
  import type { QueuedMutation } from './commitPayload.js';
3
3
  import type { MutationStore } from './MutationStore.js';
4
4
  import type { OptimisticUpdateEntry } from './localMutation.js';
5
- import type { MutationCommitResult } from '@abloatai/transaction/wire/commit';
6
- import type { DurableCommitEnvelope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
5
+ import type { MutationCommitResult } from '@abloatai/transaction/commit';
6
+ import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
7
7
  import { applyWriteOptions, TX_TYPE_TO_MUTATION_OP } from './commitPayload.js';
8
8
 
9
9
  export interface PendingDrainContext {
@@ -23,14 +23,12 @@ import type { RuntimeContext } from '../../RuntimeContext.js';
23
23
  import {
24
24
  commitEnvelopeMemberSchema,
25
25
  commitOutboxScopeSchema,
26
- } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
27
- import { onStaleModeSchema } from '@abloatai/transaction/coordination/schema';
26
+ } from '@abloatai/transaction/commit';
28
27
 
29
28
  /** The subset of a write's options that is stored with each transaction or queued mutation. */
30
29
  const persistedWriteOptionsSchema = z
31
30
  .object({
32
31
  readAt: z.number().nullable().optional(),
33
- onStale: onStaleModeSchema.nullable().optional(),
34
32
  idempotencyKey: z.string().optional(),
35
33
  label: z.string().optional(),
36
34
  // Aligned with the `WriteOptions` type: a claimed write persisted locally
@@ -1,5 +1,3 @@
1
- import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
2
-
3
1
  type ModelData = Record<string, unknown>;
4
2
 
5
3
  /** One mutation retained in the durable local transaction journal. */
@@ -10,7 +8,6 @@ interface PersistedMutation {
10
8
  timestamp: string;
11
9
  writeOptions?: {
12
10
  readAt?: number | null;
13
- onStale?: OnStaleMode | null;
14
11
  };
15
12
  }
16
13
 
package/src/plugin.ts CHANGED
@@ -19,9 +19,9 @@ import type { ParticipantKind } from '@abloatai/transaction/types/participant';
19
19
  import type { ErrorCodeSpec } from '@abloatai/transaction/errorCodes';
20
20
  import type { Logger } from '@abloatai/transaction/logger';
21
21
  import type { CoordinationObservability } from '@abloatai/transaction/observability';
22
- import type { WsTransport } from '@abloatai/transaction/transport/wsTransport';
22
+ import type { WsTransport } from '@abloatai/transaction/transport/websocket';
23
23
  import type { AuthCredentialSource } from '@abloatai/transaction/auth/credentialSource';
24
- import type { SyncDeltaWireCore } from '@abloatai/transaction/wire/delta';
24
+ import type { SyncDeltaWireCore } from '@abloatai/transaction/observation';
25
25
  import type { ModelData } from '@abloatai/transaction/types/modelData';
26
26
 
27
27
  /**
@@ -23,8 +23,8 @@
23
23
  * `leave`.
24
24
  */
25
25
 
26
- import type { WsTransport } from '@abloatai/transaction/transport/wsTransport';
27
- import type { PresenceUpdate } from '@abloatai/transaction/transport/wsTransport';
26
+ import type { WsTransport } from '@abloatai/transaction/transport/websocket';
27
+ import type { PresenceUpdate } from '@abloatai/transaction/transport/websocket';
28
28
  import type {
29
29
  Activity,
30
30
  Peer,
@@ -36,7 +36,7 @@ import {
36
36
  type UseAbloModelResult,
37
37
  } from './useAblo.js';
38
38
  import type { AbloClient as Ablo } from '../client.js';
39
- import type { ModelOperations } from '../local/client/createModelProxy.js';
39
+ import type { ModelOperations } from '../local/client/createModelOperations.js';
40
40
  import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
41
41
 
42
42
  /** What a binding returns: the provider and the hook, with `S` fixed. */
@@ -7,7 +7,7 @@ import type { ModelClaim } from '@abloatai/transaction/coordination';
7
7
  import {
8
8
  getModelClientMeta,
9
9
  type ModelOperations,
10
- } from '../local/client/createModelProxy.js';
10
+ } from '../local/client/createModelOperations.js';
11
11
  import { Model } from '../local/Model.js';
12
12
  import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
13
13
  import type { ResolveSchema } from '@abloatai/transaction/types/global';
@@ -138,7 +138,7 @@ function snapshotValue<T>(value: T): T {
138
138
  * // With the Register augmentation (recommended):
139
139
  * const ablo = useAblo();
140
140
  * if (!ablo) return <Loading />;
141
- * const doc = await ablo.records.get({ id }); // async server read
141
+ * const doc = await ablo.records.get({ id }); // observational async server read
142
142
  *
143
143
  * // Reactive selector (a synchronous local snapshot). The selector's reads
144
144
  * // are typed as snapshot rows — data fields + computeds, no relation
package/src/surface.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * written by hand, since prose cannot be type-checked.
15
15
  */
16
16
 
17
- import type { ModelOperations, LocalReadOptions } from './local/client/createModelProxy.js';
17
+ import type { ModelOperations, LocalReadOptions } from './local/client/createModelOperations.js';
18
18
  import type { AbloOptions } from './Ablo.js';
19
19
 
20
20
  // ── compile-time exact-equality (no runtime, no casts) ─────────────────────
@@ -33,14 +33,14 @@ type Expect<T extends true> = T;
33
33
  */
34
34
  export const PUBLIC_MODEL_VERBS = [
35
35
  'get',
36
- 'retrieve',
36
+ 'read',
37
37
  'list',
38
+ 'listAll',
38
39
  'local',
39
40
  'create',
40
41
  'update',
41
42
  'delete',
42
43
  'claim',
43
- 'track',
44
44
  'join',
45
45
  'onChange',
46
46
  ] as const;
@@ -1,29 +0,0 @@
1
- /**
2
- * Captures a local {@link Snapshot} of a chosen set of entities, along with a
3
- * watermark, so a caller can detect when that state has gone stale. This is
4
- * what an LLM caller threads into a prompt: `stamp` flows into later writes as
5
- * `readAt`, so the server rejects a mutation premised on data that has since
6
- * changed; `signal` is an `AbortSignal` that fires as soon as any captured
7
- * entity receives a delta, so a mid-generation invalidation can abort the token
8
- * stream instead of producing output against stale context.
9
- *
10
- * It reads the current entity state from the in-memory pool, reads the engine's
11
- * current `lastSyncId` as the watermark, and subscribes to delta frames on the
12
- * existing sync connection — no second connection.
13
- */
14
- import type { InstanceCache } from '../InstanceCache.js';
15
- import type { Schema } from '@abloatai/transaction/schema/schema';
16
- import type { SyncWebSocket } from './SyncWebSocket.js';
17
- import type { Snapshot } from '@abloatai/transaction/types/streams';
18
- export interface CreateSnapshotArgs<TSchema extends Schema = Schema, K extends keyof TSchema['models'] & string = keyof TSchema['models'] & string> {
19
- pool: InstanceCache;
20
- /** Live transport for delta subscriptions. May be null if the engine
21
- * hasn't connected yet — the snapshot still resolves with current
22
- * pool state, but `signal` won't fire until reconnect. */
23
- transport: SyncWebSocket | null;
24
- /** Returns the engine's current `lastSyncId`. Read at snapshot time
25
- * to stamp the watermark; not re-read after. */
26
- getLastSyncId: () => number;
27
- entities: Readonly<Record<K, string | readonly string[]>>;
28
- }
29
- export declare function createSnapshot<TSchema extends Schema, K extends keyof TSchema['models'] & string>(args: CreateSnapshotArgs<TSchema, K>): Snapshot<TSchema, K>;