@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.
- package/dist/Ablo.d.ts +8 -18
- package/dist/client.d.ts +6 -19
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/local/Database.d.ts +1 -1
- package/dist/local/Model.js +1 -1
- package/dist/local/SyncClient.d.ts +3 -34
- package/dist/local/SyncClient.js +6 -16
- package/dist/local/client/createInternalComponents.d.ts +1 -1
- package/dist/local/client/createInternalComponents.js +1 -1
- package/dist/local/client/{createModelProxy.d.ts → createModelOperations.d.ts} +22 -9
- package/dist/local/client/{createModelProxy.js → createModelOperations.js} +341 -335
- package/dist/local/client/options.d.ts +2 -2
- package/dist/local/client/reactiveEngine.js +38 -45
- package/dist/local/client/resourceTypes.d.ts +3 -3
- package/dist/local/client/resourceTypes.js +1 -1
- package/dist/local/client/wsMutationExecutor.d.ts +4 -4
- package/dist/local/client/wsMutationExecutor.js +4 -5
- package/dist/local/coordination/ClaimLog.d.ts +3 -3
- package/dist/local/coordination/ClaimLog.js +2 -2
- package/dist/local/interfaces/index.d.ts +5 -7
- package/dist/local/mutators/mutateActions.d.ts +3 -2
- package/dist/local/query/types.d.ts +2 -2
- package/dist/local/stores/syncAction.d.ts +1 -1
- package/dist/local/stores/syncAction.js +1 -1
- package/dist/local/sync/ConnectionManager.d.ts +1 -1
- package/dist/local/sync/ConnectionManager.js +1 -1
- package/dist/local/sync/OnDemandLoader.d.ts +1 -1
- package/dist/local/sync/OnDemandLoader.js +1 -1
- package/dist/local/sync/SyncWebSocket.d.ts +3 -3
- package/dist/local/sync/SyncWebSocket.js +2 -2
- package/dist/local/sync/commitFrames.d.ts +1 -1
- package/dist/local/sync/commitFrames.js +1 -1
- package/dist/local/sync/contextOnChange.d.ts +8 -0
- package/dist/local/sync/contextOnChange.js +65 -0
- package/dist/local/sync/createClaimStream.d.ts +1 -1
- package/dist/local/sync/createClaimStream.js +47 -28
- package/dist/local/sync/credentialLifecycle.d.ts +1 -1
- package/dist/local/sync/credentialLifecycle.js +1 -1
- package/dist/local/sync/participants.d.ts +1 -1
- package/dist/local/sync/schemas.d.ts +2 -2
- package/dist/local/sync/schemas.js +1 -1
- package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
- package/dist/local/sync/wsFrameHandlers.js +1 -1
- package/dist/local/syncClientTypes.d.ts +41 -0
- package/dist/local/syncClientTypes.js +11 -0
- package/dist/local/transactions/databaseCommitOutbox.d.ts +2 -2
- package/dist/local/transactions/mutations/MutationQueue.d.ts +5 -18
- package/dist/local/transactions/mutations/MutationQueue.js +57 -137
- package/dist/local/transactions/mutations/batchProcessing.d.ts +2 -13
- package/dist/local/transactions/mutations/batchProcessing.js +0 -20
- package/dist/local/transactions/mutations/commitApi.d.ts +2 -3
- package/dist/local/transactions/mutations/commitApi.js +0 -4
- package/dist/local/transactions/mutations/commitLane.d.ts +6 -8
- package/dist/local/transactions/mutations/commitLane.js +12 -14
- package/dist/local/transactions/mutations/commitPayload.d.ts +2 -4
- package/dist/local/transactions/mutations/commitPayload.js +2 -6
- package/dist/local/transactions/mutations/commitTransport.d.ts +3 -4
- package/dist/local/transactions/mutations/commitTransport.js +2 -5
- package/dist/local/transactions/mutations/durableCommitRestore.d.ts +1 -1
- package/dist/local/transactions/mutations/durableCommitRestore.js +1 -4
- package/dist/local/transactions/mutations/durableWriteStore.d.ts +5 -5
- package/dist/local/transactions/mutations/durableWriteStore.js +3 -3
- package/dist/local/transactions/mutations/failurePolicy.d.ts +2 -2
- package/dist/local/transactions/mutations/mutationInput.d.ts +40 -0
- package/dist/local/transactions/mutations/mutationInput.js +53 -0
- package/dist/local/transactions/mutations/mutationPersistence.d.ts +1 -1
- package/dist/local/transactions/mutations/pendingDrain.d.ts +2 -2
- package/dist/local/transactions/mutations/replayValidation.d.ts +0 -20
- package/dist/local/transactions/mutations/replayValidation.js +1 -3
- package/dist/local/transactions/persistedTransaction.d.ts +0 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/presenceStream.d.ts +1 -1
- package/dist/react/createAbloReact.d.ts +1 -1
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useAblo.js +1 -1
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +2 -2
- package/package.json +2 -2
- package/src/Ablo.ts +9 -27
- package/src/client.ts +6 -22
- package/src/core.ts +1 -1
- package/src/local/Database.ts +1 -2
- package/src/local/Model.ts +1 -1
- package/src/local/SyncClient.ts +17 -72
- package/src/local/client/createInternalComponents.ts +2 -2
- package/src/local/client/{createModelProxy.ts → createModelOperations.ts} +396 -320
- package/src/local/client/options.ts +2 -2
- package/src/local/client/reactiveEngine.ts +67 -55
- package/src/local/client/resourceTypes.ts +4 -4
- package/src/local/client/wsMutationExecutor.ts +4 -9
- package/src/local/coordination/ClaimLog.ts +3 -3
- package/src/local/interfaces/index.ts +5 -7
- package/src/local/mutators/mutateActions.ts +3 -2
- package/src/local/query/types.ts +2 -2
- package/src/local/stores/syncAction.ts +1 -1
- package/src/local/sync/ConnectionManager.ts +1 -1
- package/src/local/sync/OnDemandLoader.ts +1 -1
- package/src/local/sync/SyncWebSocket.ts +3 -3
- package/src/local/sync/commitFrames.ts +1 -2
- package/src/local/sync/contextOnChange.ts +92 -0
- package/src/local/sync/createClaimStream.ts +77 -39
- package/src/local/sync/credentialLifecycle.ts +1 -1
- package/src/local/sync/participants.ts +1 -1
- package/src/local/sync/schemas.ts +1 -1
- package/src/local/sync/wsFrameHandlers.ts +1 -1
- package/src/local/syncClientTypes.ts +59 -0
- package/src/local/transactions/databaseCommitOutbox.ts +2 -2
- package/src/local/transactions/mutations/MutationQueue.ts +69 -183
- package/src/local/transactions/mutations/batchProcessing.ts +2 -38
- package/src/local/transactions/mutations/commitApi.ts +3 -7
- package/src/local/transactions/mutations/commitLane.ts +24 -20
- package/src/local/transactions/mutations/commitPayload.ts +3 -11
- package/src/local/transactions/mutations/commitTransport.ts +3 -7
- package/src/local/transactions/mutations/durableCommitRestore.ts +2 -5
- package/src/local/transactions/mutations/durableWriteStore.ts +5 -5
- package/src/local/transactions/mutations/failurePolicy.ts +2 -2
- package/src/local/transactions/mutations/mutationInput.ts +69 -0
- package/src/local/transactions/mutations/mutationPersistence.ts +1 -1
- package/src/local/transactions/mutations/pendingDrain.ts +2 -2
- package/src/local/transactions/mutations/replayValidation.ts +1 -3
- package/src/local/transactions/persistedTransaction.ts +0 -3
- package/src/plugin.ts +2 -2
- package/src/presenceStream.ts +2 -2
- package/src/react/createAbloReact.ts +1 -1
- package/src/react/useAblo.ts +2 -2
- package/src/surface.ts +3 -3
- package/dist/local/sync/createSnapshot.d.ts +0 -29
- package/dist/local/sync/createSnapshot.js +0 -116
- package/src/local/sync/createSnapshot.ts +0 -160
|
@@ -14,19 +14,17 @@
|
|
|
14
14
|
import { EventEmitter } from 'events';
|
|
15
15
|
import { v4 as uuid } from 'uuid';
|
|
16
16
|
import { globalRuntime } from '../../context.js';
|
|
17
|
-
import {
|
|
17
|
+
import { AbloConnectionError, AbloIdempotencyError, } from '@abloatai/transaction/errors';
|
|
18
18
|
import { LogPosition, } from '../../logPosition.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
hasCommitCoalescingBarrier, applyWriteOptions, asTransportError, extractStatusCode, TX_TYPE_TO_MUTATION_OP, } from './commitPayload.js';
|
|
19
|
+
import {} from '@abloatai/transaction/commit';
|
|
20
|
+
import { computePriorityScore, normalizeModelKey, } from './commitPayload.js';
|
|
21
|
+
import { generateTransactionId, mergeMutationData, createDataFor, changesToInput, updateDataFor, previousDataFor, } from './mutationInput.js';
|
|
23
22
|
import { MutationStore } from './MutationStore.js';
|
|
24
|
-
import { entityKey,
|
|
23
|
+
import { entityKey, takeUnsentCreateForModel, findCreateBarrierForDelete, deferDeleteUntilCreateSettles, releaseDeferredDeletesForCreate, } from './coalesceRules.js';
|
|
25
24
|
import { DeltaConfirmationTracker } from './deltaConfirmation.js';
|
|
26
|
-
import { deserializePersistedTransaction, isNonReplayablePersistedRow, pendingMutationRecordId,
|
|
25
|
+
import { deserializePersistedTransaction, isNonReplayablePersistedRow, pendingMutationRecordId, } from './replayValidation.js';
|
|
27
26
|
import { deserializeLegacyPendingMutation, loadPersistedTransactions, persistQueuedTransaction, removePersistedTransaction, settlePersistedFailure, } from './mutationPersistence.js';
|
|
28
|
-
import { createCommitEnvelopeMember,
|
|
29
|
-
import { stableStringify } from '@abloatai/transaction/utils/json';
|
|
27
|
+
import { createCommitEnvelopeMember, } from '@abloatai/transaction/commit';
|
|
30
28
|
import { createLocalMutationPort, } from './localMutation.js';
|
|
31
29
|
import { dispatchCommitBounded, parseMutationCommitResult, persistDurableCommitAcceptance, removeDurableCommit, sealDurableCommit, } from './commitTransport.js';
|
|
32
30
|
import { processCommitLane, waitForCommitReceipt, } from './commitLane.js';
|
|
@@ -120,7 +118,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
120
118
|
commitOutbox: this.commitOutbox,
|
|
121
119
|
commitOutboxScope: this.commitOutboxScope,
|
|
122
120
|
mutationExecutor: this.mutationExecutor,
|
|
123
|
-
emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
|
|
121
|
+
emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
|
|
124
122
|
};
|
|
125
123
|
}
|
|
126
124
|
get commitLaneContext() {
|
|
@@ -132,24 +130,22 @@ export class MutationQueue extends EventEmitter {
|
|
|
132
130
|
retryBackoff: this.config.retryBackoff,
|
|
133
131
|
},
|
|
134
132
|
commitLane: this.commitLane,
|
|
135
|
-
commitNotifications: this.commitNotifications,
|
|
136
133
|
commitMissingIds: this.commitMissingIds,
|
|
137
134
|
commitProcessing: this.commitProcessing,
|
|
138
135
|
setCommitProcessing: (value) => { this.commitProcessing = value; },
|
|
139
136
|
durableReplayBlock: this.durableReplayBlock,
|
|
140
137
|
sealDurableCommit: (input) => this.sealDurableCommit(input),
|
|
141
|
-
assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
|
|
138
|
+
assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
|
|
142
139
|
dispatchCommit: async (envelope) => this.parseMutationCommitResult(await this.dispatchCommitBounded(envelope.operations, {
|
|
143
140
|
idempotencyKey: envelope.idempotencyKey,
|
|
144
141
|
...(envelope.commitOptions.reads ? { reads: envelope.commitOptions.reads } : {}),
|
|
145
|
-
...(envelope.commitOptions.track ? { track: envelope.commitOptions.track } : {}),
|
|
146
142
|
})),
|
|
147
143
|
persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
|
|
148
144
|
removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
|
|
149
145
|
queuedCommitEchoSyncId: (transaction) => this.queuedCommitEchoSyncId(transaction),
|
|
150
|
-
completeQueuedCommit: (transaction, syncId) => this.completeQueuedCommit(transaction, syncId),
|
|
151
|
-
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId),
|
|
152
|
-
noteAck: (syncId) => this.noteAck(syncId),
|
|
146
|
+
completeQueuedCommit: (transaction, syncId) => { this.completeQueuedCommit(transaction, syncId); },
|
|
147
|
+
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
|
|
148
|
+
noteAck: (syncId) => { this.noteAck(syncId); },
|
|
153
149
|
isDefinitiveRejection: (error) => this.isDefinitiveRejection(error),
|
|
154
150
|
isPermanentError: (error) => this.isPermanentError(error),
|
|
155
151
|
scheduleRetry: (delayMs) => {
|
|
@@ -160,13 +156,12 @@ export class MutationQueue extends EventEmitter {
|
|
|
160
156
|
void this.processCommitLane();
|
|
161
157
|
}, delayMs);
|
|
162
158
|
},
|
|
163
|
-
emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
|
|
159
|
+
emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
|
|
164
160
|
};
|
|
165
161
|
}
|
|
166
162
|
get commitReceiptContext() {
|
|
167
163
|
return {
|
|
168
164
|
commitStore: this.commitStore,
|
|
169
|
-
commitNotifications: this.commitNotifications,
|
|
170
165
|
commitMissingIds: this.commitMissingIds,
|
|
171
166
|
replicationLagErrors: this.replicationLagErrors,
|
|
172
167
|
on: (event, listener) => this.on(event, listener),
|
|
@@ -175,22 +170,22 @@ export class MutationQueue extends EventEmitter {
|
|
|
175
170
|
}
|
|
176
171
|
get commitApiContext() {
|
|
177
172
|
return {
|
|
178
|
-
assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
|
|
173
|
+
assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
|
|
179
174
|
commitStore: this.commitStore,
|
|
180
175
|
commitLane: this.commitLane,
|
|
181
176
|
replicationLagErrors: this.replicationLagErrors,
|
|
182
|
-
clearReplicationLagState: (transactionId) => this.clearReplicationLagState(transactionId),
|
|
177
|
+
clearReplicationLagState: (transactionId) => { this.clearReplicationLagState(transactionId); },
|
|
183
178
|
nextCommitSequence: () => this.nextCommitSequence(),
|
|
184
179
|
sealDurableCommit: (input) => this.sealDurableCommit(input),
|
|
185
180
|
processCommitLane: () => this.processCommitLane(),
|
|
186
|
-
emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
|
|
181
|
+
emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
|
|
187
182
|
};
|
|
188
183
|
}
|
|
189
184
|
get modelMutationContext() {
|
|
190
185
|
return {
|
|
191
186
|
enableOptimistic: this.config.enableOptimistic,
|
|
192
187
|
persistenceReady: !!this.persistence && !!this.commitOutboxScope,
|
|
193
|
-
assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
|
|
188
|
+
assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
|
|
194
189
|
generateId: () => this.generateId(),
|
|
195
190
|
normalizeModelKey,
|
|
196
191
|
computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
|
|
@@ -199,11 +194,11 @@ export class MutationQueue extends EventEmitter {
|
|
|
199
194
|
extractPreviousData: (model, input) => this.extractPreviousData(model, input),
|
|
200
195
|
mapChangesToInput: (modelName, changes) => this.mapChangesToInput(modelName, changes),
|
|
201
196
|
isReorderPayload: (input) => this.isReorderPayload(input),
|
|
202
|
-
attachConfirmation: (transaction) => this.attachConfirmation(transaction),
|
|
203
|
-
add: (transaction) => this.store.add(transaction),
|
|
204
|
-
applyOptimisticCreate: (model, transaction) => this.applyOptimisticCreate(model, transaction),
|
|
205
|
-
applyOptimisticUpdate: (model, transaction) => this.applyOptimisticUpdate(model, transaction),
|
|
206
|
-
applyOptimisticDelete: (model, transaction) => this.applyOptimisticDelete(model, transaction),
|
|
197
|
+
attachConfirmation: (transaction) => { this.attachConfirmation(transaction); },
|
|
198
|
+
add: (transaction) => { this.store.add(transaction); },
|
|
199
|
+
applyOptimisticCreate: (model, transaction) => { this.applyOptimisticCreate(model, transaction); },
|
|
200
|
+
applyOptimisticUpdate: (model, transaction) => { this.applyOptimisticUpdate(model, transaction); },
|
|
201
|
+
applyOptimisticDelete: (model, transaction) => { this.applyOptimisticDelete(model, transaction); },
|
|
207
202
|
takeUnsentCreateForModel: (modelName, modelId) => this.takeUnsentCreateForModel(modelName, modelId),
|
|
208
203
|
cancelUnsentCreateForDelete: (transaction) => this.cancelUnsentCreateForDelete(transaction),
|
|
209
204
|
completeLocalDelete: (model, context, writeOptions, sourceMutationIds) => this.completeLocalDelete(model, context, writeOptions, sourceMutationIds),
|
|
@@ -211,11 +206,11 @@ export class MutationQueue extends EventEmitter {
|
|
|
211
206
|
pendingMergeByModel: this.pendingMergeByModel,
|
|
212
207
|
inFlightByModel: this.inFlightByModel,
|
|
213
208
|
findCreateBarrierForDelete: (modelName, modelId) => this.findCreateBarrierForDelete(modelName, modelId),
|
|
214
|
-
deferDeleteUntilCreateSettles: (create, transaction) => this.deferDeleteUntilCreateSettles(create, transaction),
|
|
209
|
+
deferDeleteUntilCreateSettles: (create, transaction) => { this.deferDeleteUntilCreateSettles(create, transaction); },
|
|
215
210
|
logger: this.runtime.logger,
|
|
216
211
|
persistAndStage: (transaction, modelData) => this.persistAndStage(transaction, modelData),
|
|
217
212
|
persistQueuedTransaction: (transaction, modelData) => this.persistQueuedTransaction(transaction, modelData),
|
|
218
|
-
stageTransaction: (transaction) => this.stageTransaction(transaction),
|
|
213
|
+
stageTransaction: (transaction) => { this.stageTransaction(transaction); },
|
|
219
214
|
emit: (event, payload) => this.emit(event, payload),
|
|
220
215
|
};
|
|
221
216
|
}
|
|
@@ -224,9 +219,9 @@ export class MutationQueue extends EventEmitter {
|
|
|
224
219
|
executionQueue: this.executionQueue,
|
|
225
220
|
inFlightByModel: this.inFlightByModel,
|
|
226
221
|
pendingMergeByModel: this.pendingMergeByModel,
|
|
227
|
-
ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
|
|
228
|
-
scheduleProcessing: (immediate) => this.scheduleProcessing(immediate),
|
|
229
|
-
storeRemove: (transactionId) => this.store.remove(transactionId),
|
|
222
|
+
ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
|
|
223
|
+
scheduleProcessing: (immediate) => { this.scheduleProcessing(immediate); },
|
|
224
|
+
storeRemove: (transactionId) => { this.store.remove(transactionId); },
|
|
230
225
|
};
|
|
231
226
|
}
|
|
232
227
|
get batchProcessingContext() {
|
|
@@ -238,7 +233,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
238
233
|
isProcessing: this.isProcessing,
|
|
239
234
|
setIsProcessing: (value) => { this.isProcessing = value; },
|
|
240
235
|
takeNextExecutionBatch: () => this.takeNextExecutionBatch(),
|
|
241
|
-
ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
|
|
236
|
+
ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
|
|
242
237
|
ensureCommitEnvelope: (batch) => this.ensureCommitEnvelope([...batch]),
|
|
243
238
|
executingCount: this.executingCount,
|
|
244
239
|
setExecutingCount: (value) => { this.executingCount = value; },
|
|
@@ -247,31 +242,28 @@ export class MutationQueue extends EventEmitter {
|
|
|
247
242
|
generateId: () => this.generateId(),
|
|
248
243
|
computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
|
|
249
244
|
store: this.store,
|
|
250
|
-
enqueue: (transaction) => this.enqueue(transaction),
|
|
245
|
+
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
251
246
|
optimisticUpdates: this.localMutationPort.updates,
|
|
252
|
-
commitNotifications: this.commitNotifications,
|
|
253
247
|
commitMissingIds: this.commitMissingIds,
|
|
254
248
|
sourceMutationIdsFor: (batch) => this.sourceMutationIdsFor(batch),
|
|
255
249
|
dispatchCommitBounded: (...args) => this.dispatchCommitBounded(...args),
|
|
256
250
|
parseMutationCommitResult: (value) => this.parseMutationCommitResult(value),
|
|
257
251
|
persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
|
|
258
252
|
removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
|
|
259
|
-
assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
|
|
253
|
+
assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
|
|
260
254
|
sealDurableCommit: (input) => this.sealDurableCommit(input),
|
|
261
|
-
noteAck: (syncId) => this.noteAck(syncId),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
clearReplicationLagState: (transactionId) => this.clearReplicationLagState(transactionId),
|
|
267
|
-
completeQueuedCommit: (transaction, syncId) => this.completeQueuedCommit(transaction, syncId),
|
|
255
|
+
noteAck: (syncId) => { this.noteAck(syncId); },
|
|
256
|
+
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
|
|
257
|
+
scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => { this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs); },
|
|
258
|
+
clearReplicationLagState: (transactionId) => { this.clearReplicationLagState(transactionId); },
|
|
259
|
+
completeQueuedCommit: (transaction, syncId) => { this.completeQueuedCommit(transaction, syncId); },
|
|
268
260
|
queuedCommitMatchesCorrelation: (transaction, correlationId) => this.queuedCommitMatchesCorrelation(transaction, correlationId),
|
|
269
261
|
recentDeltaCorrelations: this.recentDeltaCorrelations,
|
|
270
262
|
lastSeenSyncId: this.lastSeenSyncId,
|
|
271
|
-
scheduleProcessing: (immediate) => this.scheduleProcessing(immediate),
|
|
263
|
+
scheduleProcessing: (immediate) => { this.scheduleProcessing(immediate); },
|
|
272
264
|
handleFailure: (transaction, error) => this.handleFailure(transaction, error),
|
|
273
265
|
isDefinitiveRejection: (error) => this.isDefinitiveRejection(error),
|
|
274
|
-
emitCommitLifecycle: (event, payload) => this.emitCommitLifecycle(event, payload),
|
|
266
|
+
emitCommitLifecycle: (event, payload) => { this.emitCommitLifecycle(event, payload); },
|
|
275
267
|
emit: (event, payload) => this.emit(event, payload),
|
|
276
268
|
rollbackOptimistic: (transaction, reason, error) => this.rollbackOptimistic(transaction, reason, error),
|
|
277
269
|
};
|
|
@@ -283,19 +275,19 @@ export class MutationQueue extends EventEmitter {
|
|
|
283
275
|
store: this.store,
|
|
284
276
|
isPermanentError: (error) => this.isPermanentError(error),
|
|
285
277
|
rollbackOptimistic: (transaction, reason, error) => this.rollbackOptimistic(transaction, reason, error),
|
|
286
|
-
enqueue: (transaction) => this.enqueue(transaction),
|
|
278
|
+
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
287
279
|
getLastPermanentErrorSignature: () => this.lastPermanentErrorSig,
|
|
288
280
|
setLastPermanentErrorSignature: (signature) => { this.lastPermanentErrorSig = signature; },
|
|
289
281
|
emit: (event, payload) => this.emit(event, payload),
|
|
290
282
|
};
|
|
291
283
|
}
|
|
292
|
-
get
|
|
284
|
+
get conflictResolutionContext() {
|
|
293
285
|
return {
|
|
294
286
|
config: this.config,
|
|
295
287
|
store: this.store,
|
|
296
288
|
rollbackOptimistic: (transaction, reason) => this.rollbackOptimistic(transaction, reason),
|
|
297
289
|
mergeData: (local, remote) => this.mergeData(local, remote),
|
|
298
|
-
enqueue: (transaction) => this.enqueue(transaction),
|
|
290
|
+
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
299
291
|
};
|
|
300
292
|
}
|
|
301
293
|
get processingSchedulerContext() {
|
|
@@ -318,21 +310,21 @@ export class MutationQueue extends EventEmitter {
|
|
|
318
310
|
store: this.store,
|
|
319
311
|
executionQueue: this.executionQueue,
|
|
320
312
|
optimisticUpdates: this.localMutationPort.updates,
|
|
321
|
-
assertDurableReplayOpen: () => this.assertDurableReplayOpen(),
|
|
313
|
+
assertDurableReplayOpen: () => { this.assertDurableReplayOpen(); },
|
|
322
314
|
processCommitLane: () => this.processCommitLane(),
|
|
323
315
|
takePendingDrainBatch: (pending) => this.takePendingDrainBatch(pending),
|
|
324
316
|
ensureCommitEnvelope: (batch) => this.ensureCommitEnvelope(batch),
|
|
325
|
-
ensureDerivedFields: (transaction) => this.ensureDerivedFields(transaction),
|
|
317
|
+
ensureDerivedFields: (transaction) => { this.ensureDerivedFields(transaction); },
|
|
326
318
|
sourceMutationIdsFor: (batch) => this.sourceMutationIdsFor(batch),
|
|
327
319
|
sealDurableCommit: (input) => this.sealDurableCommit(input),
|
|
328
|
-
assertEnvelopeInsideReplayWindow: (envelope) => this.assertEnvelopeInsideReplayWindow(envelope),
|
|
320
|
+
assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
|
|
329
321
|
parseMutationCommitResult: (value) => this.parseMutationCommitResult(value),
|
|
330
322
|
dispatchCommitBounded: (...args) => this.dispatchCommitBounded(...args),
|
|
331
323
|
persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
|
|
332
324
|
removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
|
|
333
|
-
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId),
|
|
334
|
-
scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs),
|
|
335
|
-
enqueue: (transaction) => this.enqueue(transaction),
|
|
325
|
+
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
|
|
326
|
+
scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => { this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs); },
|
|
327
|
+
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
336
328
|
recentDeltaCorrelations: this.recentDeltaCorrelations,
|
|
337
329
|
emit: (event, payload) => this.emit(event, payload),
|
|
338
330
|
};
|
|
@@ -356,7 +348,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
356
348
|
commitOutboxScope: this.commitOutboxScope,
|
|
357
349
|
config: this.config,
|
|
358
350
|
store: this.store,
|
|
359
|
-
enqueue: (transaction) => this.enqueue(transaction),
|
|
351
|
+
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
360
352
|
computePriorityScore: (type, modelName) => this.computePriorityScore(type, modelName),
|
|
361
353
|
deserializeTransaction: (data) => this.deserializeTransaction(data),
|
|
362
354
|
};
|
|
@@ -410,59 +402,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
410
402
|
entityKey(modelName, modelId) {
|
|
411
403
|
return entityKey(modelName, modelId);
|
|
412
404
|
}
|
|
413
|
-
|
|
414
|
-
receiptTargetKey(modelName, modelId) {
|
|
415
|
-
return stableStringify([normalizeModelKey(modelName), modelId]);
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Relates stale notifications back to write targets without assuming the
|
|
419
|
-
* server's canonical model name uses the same spelling as the public schema
|
|
420
|
-
* key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
|
|
421
|
-
* is the compatibility fallback. An ambiguous same-id cross-model mismatch
|
|
422
|
-
* is deliberately left unclassified, so it cannot falsely settle a queued
|
|
423
|
-
* write. A notification with no write-target id (or an explicit group) is a
|
|
424
|
-
* declared-read conflict and holds the whole batch.
|
|
425
|
-
*/
|
|
426
|
-
classifyReceiptNotifications(operations, notifications) {
|
|
427
|
-
const targets = operations.map((operation) => ({
|
|
428
|
-
id: operation.id,
|
|
429
|
-
key: this.receiptTargetKey(operation.model, operation.id),
|
|
430
|
-
}));
|
|
431
|
-
const heldTargets = new Set();
|
|
432
|
-
const notificationsByTarget = new Map();
|
|
433
|
-
let holdsEntireBatch = false;
|
|
434
|
-
for (const notification of notifications) {
|
|
435
|
-
// Scope is decided before any target matching. A group premise fires over
|
|
436
|
-
// the WHOLE batch by convention, and its `target` now names the row that
|
|
437
|
-
// actually moved — which may well be a row this batch is writing, so
|
|
438
|
-
// matching first would misread a batch-wide hold as a per-row one.
|
|
439
|
-
if (notification.scope === 'group') {
|
|
440
|
-
holdsEntireBatch = true;
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
const candidates = targets.filter((target) => target.id === notification.target.id);
|
|
444
|
-
const notificationKey = this.receiptTargetKey(notification.target.model, notification.target.id);
|
|
445
|
-
const exactTargets = candidates.filter((target) => target.key === notificationKey);
|
|
446
|
-
const candidateKeys = new Set((exactTargets.length > 0 ? exactTargets : candidates).map((target) => target.key));
|
|
447
|
-
if (candidates.length === 0) {
|
|
448
|
-
holdsEntireBatch = true;
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
if (candidateKeys.size !== 1) {
|
|
452
|
-
// Same id across multiple differently named models with no exact match:
|
|
453
|
-
// the id-only compatibility fallback is ambiguous. Await the echo.
|
|
454
|
-
continue;
|
|
455
|
-
}
|
|
456
|
-
const [targetKey] = candidateKeys;
|
|
457
|
-
if (!targetKey)
|
|
458
|
-
continue;
|
|
459
|
-
heldTargets.add(targetKey);
|
|
460
|
-
const targetNotifications = notificationsByTarget.get(targetKey) ?? [];
|
|
461
|
-
targetNotifications.push(notification);
|
|
462
|
-
notificationsByTarget.set(targetKey, targetNotifications);
|
|
463
|
-
}
|
|
464
|
-
return { holdsEntireBatch, heldTargets, notificationsByTarget };
|
|
465
|
-
}
|
|
405
|
+
/* Stale receipt classification was removed; stale premises now reject. */
|
|
466
406
|
resolveConfirmation(transaction) {
|
|
467
407
|
const resolver = this.confirmationResolvers.get(transaction.id);
|
|
468
408
|
if (!resolver)
|
|
@@ -544,11 +484,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
544
484
|
// Track executing transactions for backpressure
|
|
545
485
|
executingCount = 0;
|
|
546
486
|
localMutationPort;
|
|
547
|
-
// Stale-context notifications, keyed by transaction id. When the server
|
|
548
|
-
// accepts a commit but reports that an operation's premise had moved,
|
|
549
|
-
// the notification lands here from the commit acknowledgement and is drained
|
|
550
|
-
// by `waitForCommitReceipt`, so the receipt can carry it back to the caller.
|
|
551
|
-
commitNotifications = new Map();
|
|
552
487
|
/** Zero-row targets returned on a successful atomic commit receipt. */
|
|
553
488
|
commitMissingIds = new Map();
|
|
554
489
|
// Delta-confirmation tracking (ack watermark advance + the awaiting_delta
|
|
@@ -1267,7 +1202,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
1267
1202
|
await handleFailure(this.failureHandlingContext, transaction, error);
|
|
1268
1203
|
}
|
|
1269
1204
|
async handleConflict(transaction, serverData) {
|
|
1270
|
-
await resolveConflict(this.
|
|
1205
|
+
await resolveConflict(this.conflictResolutionContext, transaction, serverData);
|
|
1271
1206
|
}
|
|
1272
1207
|
applyOptimisticCreate(model, transaction) {
|
|
1273
1208
|
this.localMutationPort.applyCreate(model, transaction);
|
|
@@ -1390,39 +1325,25 @@ export class MutationQueue extends EventEmitter {
|
|
|
1390
1325
|
];
|
|
1391
1326
|
}
|
|
1392
1327
|
/** Generates a unique local transaction id. */
|
|
1328
|
+
// The payload rules live in `mutationInput`; these keep the call sites here
|
|
1329
|
+
// reading as the queue's own vocabulary.
|
|
1393
1330
|
generateId() {
|
|
1394
|
-
return
|
|
1331
|
+
return generateTransactionId();
|
|
1395
1332
|
}
|
|
1396
1333
|
mergeData(local, remote) {
|
|
1397
|
-
return
|
|
1334
|
+
return mergeMutationData(local, remote);
|
|
1398
1335
|
}
|
|
1399
1336
|
extractCreateData(model) {
|
|
1400
|
-
return
|
|
1337
|
+
return createDataFor(model, this.runtime);
|
|
1401
1338
|
}
|
|
1402
1339
|
mapChangesToInput(modelName, changes) {
|
|
1403
|
-
return
|
|
1340
|
+
return changesToInput(modelName, changes, this.runtime);
|
|
1404
1341
|
}
|
|
1405
1342
|
extractUpdateData(model) {
|
|
1406
|
-
return
|
|
1343
|
+
return updateDataFor(model, this.runtime);
|
|
1407
1344
|
}
|
|
1408
|
-
// Derive previous values for changed fields to support accurate rollback.
|
|
1409
|
-
// Model-specific special cases do not belong here; a model that needs to
|
|
1410
|
-
// surface previous state beyond `modifiedProperties` should expose a typed
|
|
1411
|
-
// `getPreviousData()` accessor for this method to call.
|
|
1412
1345
|
extractPreviousData(model, updateInput) {
|
|
1413
|
-
|
|
1414
|
-
// exactly those keys, so the recorded undo inverse reverts them and nothing
|
|
1415
|
-
// else — a full-row inverse would clobber concurrent edits to unrelated
|
|
1416
|
-
// fields. `fallbackToLive: false` makes `Model.capturePreviousValues` omit
|
|
1417
|
-
// any key it cannot resolve, and `buildUndoOps` then drops an un-revertible
|
|
1418
|
-
// inverse rather than inventing one. With no `updateInput` (a full extract)
|
|
1419
|
-
// it falls back to every tracked field. `Model.capturePreviousValues` is the
|
|
1420
|
-
// single before-image source, shared with
|
|
1421
|
-
// `RecordingMutation.snapshotFields`.
|
|
1422
|
-
const keys = updateInput
|
|
1423
|
-
? Object.keys(updateInput)
|
|
1424
|
-
: [...(model.modifiedProperties instanceof Map ? model.modifiedProperties.keys() : [])];
|
|
1425
|
-
return { id: model.id, ...model.capturePreviousValues(keys, { fallbackToLive: false }) };
|
|
1346
|
+
return previousDataFor(model, updateInput);
|
|
1426
1347
|
}
|
|
1427
1348
|
/** Returns a snapshot of queue counts and the current configuration. */
|
|
1428
1349
|
getStats() {
|
|
@@ -1533,7 +1454,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
1533
1454
|
this.recentDeltaCorrelations.clear();
|
|
1534
1455
|
this.commitLane = [];
|
|
1535
1456
|
this.commitStore.clear();
|
|
1536
|
-
this.commitNotifications.clear();
|
|
1537
1457
|
this.commitMissingIds.clear();
|
|
1538
1458
|
// Clear event listeners
|
|
1539
1459
|
this.removeAllListeners();
|
|
@@ -3,9 +3,8 @@ 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 {
|
|
7
|
-
import type {
|
|
8
|
-
import type { DurableCommitEnvelope } from '@abloatai/transaction/transactions/confirmation/commitEnvelope';
|
|
6
|
+
import type { MutationCommitResult } from '@abloatai/transaction/commit';
|
|
7
|
+
import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
|
|
9
8
|
export interface BatchProcessingContext {
|
|
10
9
|
readonly runtime: RuntimeContext;
|
|
11
10
|
readonly config: MutationQueueConfig;
|
|
@@ -28,7 +27,6 @@ export interface BatchProcessingContext {
|
|
|
28
27
|
readonly store: MutationStore;
|
|
29
28
|
readonly enqueue: (transaction: QueuedMutation) => void;
|
|
30
29
|
readonly optimisticUpdates: Map<string, OptimisticUpdateEntry>;
|
|
31
|
-
readonly commitNotifications: Map<string, StaleNotification[]>;
|
|
32
30
|
readonly commitMissingIds: Map<string, string[]>;
|
|
33
31
|
readonly sourceMutationIdsFor: (batch: readonly QueuedMutation[]) => string[];
|
|
34
32
|
readonly dispatchCommitBounded: (...args: Parameters<import('../../interfaces/index.js').MutationExecutor['commit']>) => ReturnType<import('../../interfaces/index.js').MutationExecutor['commit']>;
|
|
@@ -38,15 +36,6 @@ export interface BatchProcessingContext {
|
|
|
38
36
|
readonly assertEnvelopeInsideReplayWindow: (envelope: DurableCommitEnvelope) => void;
|
|
39
37
|
readonly sealDurableCommit: (input: Parameters<typeof import('./commitTransport.js').sealDurableCommit>[1]) => Promise<DurableCommitEnvelope>;
|
|
40
38
|
readonly noteAck: (syncId: number | undefined) => void;
|
|
41
|
-
readonly classifyReceiptNotifications: (operations: readonly {
|
|
42
|
-
model: string;
|
|
43
|
-
id: string;
|
|
44
|
-
}[], notifications: readonly StaleNotification[]) => {
|
|
45
|
-
holdsEntireBatch: boolean;
|
|
46
|
-
heldTargets: Set<string>;
|
|
47
|
-
notificationsByTarget: Map<string, StaleNotification[]>;
|
|
48
|
-
};
|
|
49
|
-
readonly receiptTargetKey: (modelName: string, modelId: string) => string;
|
|
50
39
|
readonly scheduleReplicationLagTimeout: (transactionId: string, clientTxId?: string, correlationId?: string) => void;
|
|
51
40
|
readonly scheduleDeltaConfirmationTimeout: (transaction: QueuedMutation, timeoutMs: number) => void;
|
|
52
41
|
readonly clearReplicationLagState: (transactionId: string) => void;
|
|
@@ -87,17 +87,6 @@ export async function processBatch(ctx) {
|
|
|
87
87
|
}));
|
|
88
88
|
await ctx.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
89
89
|
const lastSyncId = result.lastSyncId;
|
|
90
|
-
const notifications = result.notifications;
|
|
91
|
-
const { holdsEntireBatch, heldTargets, notificationsByTarget, } = ctx.classifyReceiptNotifications(batchOps.map(({ op }) => ({ model: op.model, id: op.id })), notifications ?? []);
|
|
92
|
-
for (const { tx } of batchOps) {
|
|
93
|
-
const txTarget = ctx.receiptTargetKey(tx.modelKey, tx.modelId);
|
|
94
|
-
const txNotifs = holdsEntireBatch
|
|
95
|
-
? notifications
|
|
96
|
-
: notificationsByTarget.get(txTarget);
|
|
97
|
-
if (txNotifs && txNotifs.length > 0) {
|
|
98
|
-
ctx.commitNotifications.set(tx.id, txNotifs);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
90
|
const missingIds = new Set(result.missingIds ?? []);
|
|
102
91
|
const settlingBatchOps = [];
|
|
103
92
|
for (const entry of batchOps) {
|
|
@@ -106,15 +95,6 @@ export async function processBatch(ctx) {
|
|
|
106
95
|
await ctx.handleFailure(tx, new AbloNotFoundError(`${tx.modelName}/${tx.modelId} was not found or is outside this credential's scope.`, [tx.modelId]));
|
|
107
96
|
continue;
|
|
108
97
|
}
|
|
109
|
-
if (holdsEntireBatch ||
|
|
110
|
-
heldTargets.has(ctx.receiptTargetKey(tx.modelKey, tx.modelId))) {
|
|
111
|
-
await ctx.rollbackOptimistic(tx, 'conflict_server_wins');
|
|
112
|
-
ctx.store.updateStatus(tx.id, 'completed');
|
|
113
|
-
ctx.emit('transaction:completed', tx);
|
|
114
|
-
ctx.emit(`transaction:completed:${tx.id}`, tx);
|
|
115
|
-
ctx.optimisticUpdates.delete(tx.id);
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
98
|
settlingBatchOps.push(entry);
|
|
119
99
|
}
|
|
120
100
|
// A forwarded write has been accepted, but the source database
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ReadDependency
|
|
2
|
-
import type { DurableCommitEnvelope } from '@abloatai/transaction/
|
|
1
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
2
|
+
import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
|
|
3
3
|
import type { CommitTransaction } from './commitLane.js';
|
|
4
4
|
import type { SealDurableCommitInput } from './commitTransport.js';
|
|
5
5
|
export interface CommitApiContext {
|
|
@@ -15,5 +15,4 @@ export interface CommitApiContext {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function enqueueCommit(ctx: CommitApiContext, clientTxId: string, operations: CommitTransaction['operations'], options?: {
|
|
17
17
|
reads?: ReadDependency[] | null;
|
|
18
|
-
track?: TrackDependency[] | null;
|
|
19
18
|
}): Promise<void>;
|
|
@@ -8,12 +8,10 @@ export async function enqueueCommit(ctx, clientTxId, operations, options = {}) {
|
|
|
8
8
|
const existingIntent = stableStringify({
|
|
9
9
|
operations: existing.operations,
|
|
10
10
|
reads: existing.reads ?? null,
|
|
11
|
-
track: existing.track ?? null,
|
|
12
11
|
});
|
|
13
12
|
const incomingIntent = stableStringify({
|
|
14
13
|
operations,
|
|
15
14
|
reads: options.reads ?? null,
|
|
16
|
-
track: options.track ?? null,
|
|
17
15
|
});
|
|
18
16
|
if (existingIntent !== incomingIntent) {
|
|
19
17
|
throw new AbloIdempotencyError('Idempotency key reused with a different atomic commit request', { code: 'idempotency_conflict' });
|
|
@@ -34,7 +32,6 @@ export async function enqueueCommit(ctx, clientTxId, operations, options = {}) {
|
|
|
34
32
|
kind: 'commit',
|
|
35
33
|
operations: [...operations],
|
|
36
34
|
...(options.reads ? { reads: options.reads } : {}),
|
|
37
|
-
...(options.track ? { track: options.track } : {}),
|
|
38
35
|
status: 'pending',
|
|
39
36
|
createdAt: now,
|
|
40
37
|
attempts: 0,
|
|
@@ -48,7 +45,6 @@ export async function enqueueCommit(ctx, clientTxId, operations, options = {}) {
|
|
|
48
45
|
operations: tx.operations,
|
|
49
46
|
commitOptions: {
|
|
50
47
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
51
|
-
...(tx.track ? { track: tx.track } : {}),
|
|
52
48
|
},
|
|
53
49
|
createdAt: tx.createdAt,
|
|
54
50
|
sealedAt: tx.sealedAt,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
2
|
-
import type { ReadDependency
|
|
3
|
-
import type { MutationCommitResult } from '@abloatai/transaction/
|
|
4
|
-
import type { DurableCommitEnvelope, DurableCommitOperation } from '@abloatai/transaction/
|
|
2
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
3
|
+
import type { CommitOperationResult, MutationCommitResult } from '@abloatai/transaction/commit';
|
|
4
|
+
import type { DurableCommitEnvelope, DurableCommitOperation } from '@abloatai/transaction/commit';
|
|
5
5
|
import type { SealDurableCommitInput } from './commitTransport.js';
|
|
6
6
|
export interface CommitTransaction {
|
|
7
7
|
id: string;
|
|
@@ -13,16 +13,16 @@ export interface CommitTransaction {
|
|
|
13
13
|
input?: Record<string, unknown>;
|
|
14
14
|
transactionId?: string;
|
|
15
15
|
readAt?: number | null;
|
|
16
|
-
onStale?: OnStaleMode | null;
|
|
17
16
|
}[];
|
|
18
17
|
reads?: ReadDependency[] | null;
|
|
19
|
-
track?: TrackDependency[] | null;
|
|
20
18
|
status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed';
|
|
21
19
|
createdAt: number;
|
|
22
20
|
attempts: number;
|
|
23
21
|
transientAttempts?: number;
|
|
24
22
|
firstTransientFailureAt?: number;
|
|
25
23
|
lastSyncId?: number;
|
|
24
|
+
/** Fresh transport rows. Deliberately transient: durable server replays redact row data. */
|
|
25
|
+
operationResults?: CommitOperationResult[];
|
|
26
26
|
correlationId?: string;
|
|
27
27
|
error?: Error;
|
|
28
28
|
sealedAt: number;
|
|
@@ -42,7 +42,6 @@ export interface CommitLaneContext {
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
readonly commitLane: CommitTransaction[];
|
|
45
|
-
readonly commitNotifications: Map<string, StaleNotification[]>;
|
|
46
45
|
readonly commitMissingIds: Map<string, string[]>;
|
|
47
46
|
readonly commitProcessing: boolean;
|
|
48
47
|
readonly setCommitProcessing: (value: boolean) => void;
|
|
@@ -63,7 +62,6 @@ export interface CommitLaneContext {
|
|
|
63
62
|
}
|
|
64
63
|
export interface CommitReceiptContext {
|
|
65
64
|
readonly commitStore: Map<string, CommitTransaction>;
|
|
66
|
-
readonly commitNotifications: Map<string, StaleNotification[]>;
|
|
67
65
|
readonly commitMissingIds: Map<string, string[]>;
|
|
68
66
|
readonly replicationLagErrors: Map<string, Error>;
|
|
69
67
|
readonly on: (event: string, listener: (payload: object) => void) => void;
|
|
@@ -71,7 +69,7 @@ export interface CommitReceiptContext {
|
|
|
71
69
|
}
|
|
72
70
|
export declare function waitForCommitReceipt(ctx: CommitReceiptContext, clientTxId: string): Promise<{
|
|
73
71
|
lastSyncId: number;
|
|
74
|
-
notifications?: StaleNotification[];
|
|
75
72
|
missingIds?: string[];
|
|
73
|
+
operationResults?: CommitOperationResult[];
|
|
76
74
|
}>;
|
|
77
75
|
export declare function processCommitLane(ctx: CommitLaneContext): Promise<void>;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { transientRetryDelayMs } from './failureHandling.js';
|
|
2
2
|
export function waitForCommitReceipt(ctx, clientTxId) {
|
|
3
|
-
const drainNotifications = () => {
|
|
4
|
-
const notifications = ctx.commitNotifications.get(clientTxId);
|
|
5
|
-
if (!notifications)
|
|
6
|
-
return undefined;
|
|
7
|
-
ctx.commitNotifications.delete(clientTxId);
|
|
8
|
-
return notifications.length > 0 ? notifications : undefined;
|
|
9
|
-
};
|
|
10
3
|
const drainMissingIds = () => {
|
|
11
4
|
const ids = ctx.commitMissingIds.get(clientTxId);
|
|
12
5
|
if (!ids)
|
|
@@ -14,18 +7,20 @@ export function waitForCommitReceipt(ctx, clientTxId) {
|
|
|
14
7
|
ctx.commitMissingIds.delete(clientTxId);
|
|
15
8
|
return ids.length > 0 ? ids : undefined;
|
|
16
9
|
};
|
|
17
|
-
const receipt = (lastSyncId) => {
|
|
10
|
+
const receipt = (transaction, lastSyncId) => {
|
|
18
11
|
const missingIds = drainMissingIds();
|
|
19
12
|
return {
|
|
20
13
|
lastSyncId,
|
|
21
|
-
|
|
14
|
+
...(transaction.operationResults
|
|
15
|
+
? { operationResults: transaction.operationResults }
|
|
16
|
+
: {}),
|
|
22
17
|
...(missingIds ? { missingIds } : {}),
|
|
23
18
|
};
|
|
24
19
|
};
|
|
25
20
|
return new Promise((resolve, reject) => {
|
|
26
21
|
const existing = ctx.commitStore.get(clientTxId);
|
|
27
22
|
if (existing?.status === 'completed') {
|
|
28
|
-
resolve(receipt(existing.lastSyncId ?? 0));
|
|
23
|
+
resolve(receipt(existing, existing.lastSyncId ?? 0));
|
|
29
24
|
return;
|
|
30
25
|
}
|
|
31
26
|
if (existing?.status === 'failed' && existing.error) {
|
|
@@ -37,7 +32,11 @@ export function waitForCommitReceipt(ctx, clientTxId) {
|
|
|
37
32
|
reject(lagError);
|
|
38
33
|
return;
|
|
39
34
|
}
|
|
40
|
-
const onCompleted = (tx) => {
|
|
35
|
+
const onCompleted = (tx) => {
|
|
36
|
+
cleanup();
|
|
37
|
+
const completed = tx;
|
|
38
|
+
resolve(receipt(completed, completed.lastSyncId ?? 0));
|
|
39
|
+
};
|
|
41
40
|
const onFailed = (payload) => { cleanup(); reject(payload.error); };
|
|
42
41
|
const onLagged = (payload) => { cleanup(); reject(payload.error); };
|
|
43
42
|
const cleanup = () => {
|
|
@@ -74,7 +73,6 @@ export async function processCommitLane(ctx) {
|
|
|
74
73
|
sourceMutationIds: tx.sourceMutationIds,
|
|
75
74
|
commitOptions: {
|
|
76
75
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
77
|
-
...(tx.track ? { track: tx.track } : {}),
|
|
78
76
|
},
|
|
79
77
|
createdAt: tx.createdAt,
|
|
80
78
|
sealedAt: tx.sealedAt,
|
|
@@ -86,8 +84,8 @@ export async function processCommitLane(ctx) {
|
|
|
86
84
|
const result = await ctx.dispatchCommit(durableEnvelope);
|
|
87
85
|
tx.durableEnvelope = await ctx.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
88
86
|
tx.lastSyncId = result.lastSyncId;
|
|
89
|
-
if (result.
|
|
90
|
-
|
|
87
|
+
if (result.operationResults?.length)
|
|
88
|
+
tx.operationResults = [...result.operationResults];
|
|
91
89
|
if (result.missingIds?.length)
|
|
92
90
|
ctx.commitMissingIds.set(tx.id, result.missingIds);
|
|
93
91
|
ctx.commitLane.shift();
|