@abloatai/humans 0.57.0 → 0.59.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 +10 -1
- package/dist/local/SyncClient.js +5 -0
- 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} +11 -10
- package/dist/local/client/{createModelProxy.js → createModelOperations.js} +240 -313
- package/dist/local/client/options.d.ts +2 -2
- package/dist/local/client/reactiveEngine.js +34 -45
- package/dist/local/client/resourceTypes.d.ts +3 -3
- package/dist/local/client/resourceTypes.js +1 -1
- package/dist/local/client/schemaConfig.js +1 -0
- package/dist/local/client/wsMutationExecutor.d.ts +4 -4
- package/dist/local/client/wsMutationExecutor.js +3 -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 +10 -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/BootstrapFetcher.js +4 -4
- 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/schemaDrift.d.ts +15 -1
- package/dist/local/sync/schemaDrift.js +30 -13
- 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/transactions/databaseCommitOutbox.d.ts +2 -2
- package/dist/local/transactions/mutations/MutationQueue.d.ts +4 -19
- package/dist/local/transactions/mutations/MutationQueue.js +5 -70
- 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 +3 -8
- package/dist/local/transactions/mutations/commitLane.js +0 -11
- 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/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 +1 -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 -1
- package/src/local/Model.ts +12 -1
- package/src/local/SyncClient.ts +5 -0
- package/src/local/client/createInternalComponents.ts +2 -2
- package/src/local/client/{createModelProxy.ts → createModelOperations.ts} +292 -367
- package/src/local/client/options.ts +2 -2
- package/src/local/client/reactiveEngine.ts +63 -55
- package/src/local/client/resourceTypes.ts +4 -4
- package/src/local/client/schemaConfig.ts +3 -0
- package/src/local/client/wsMutationExecutor.ts +3 -9
- package/src/local/coordination/ClaimLog.ts +3 -3
- package/src/local/interfaces/index.ts +7 -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/BootstrapFetcher.ts +5 -4
- 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/schemaDrift.ts +32 -16
- package/src/local/sync/schemas.ts +1 -1
- package/src/local/sync/wsFrameHandlers.ts +1 -1
- package/src/local/transactions/databaseCommitOutbox.ts +2 -2
- package/src/local/transactions/mutations/MutationQueue.ts +8 -95
- 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 +3 -17
- 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/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 +2 -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
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
import { asyncIteratorFrom } from '@abloatai/transaction/utils/asyncIterator';
|
|
25
25
|
import { toMs } from '@abloatai/transaction/utils/duration';
|
|
26
26
|
import { claimDescription, descriptionFromMeta, participantKindFromWire, } from '@abloatai/transaction/coordination/schema';
|
|
27
|
-
import { isTargetTuple, subTarget, streamTarget, wireTarget, } from '@abloatai/transaction/
|
|
28
|
-
import {
|
|
27
|
+
import { isTargetTuple, subTarget, streamTarget, wireTarget, } from '@abloatai/transaction/claims';
|
|
28
|
+
import { bindClaimLifetime, createClaimLifetime, } from '@abloatai/transaction/claims/lifetime';
|
|
29
|
+
import { declaredMeta } from '@abloatai/transaction/claims';
|
|
29
30
|
import { AbloClaimedError, AbloConnectionError } from '@abloatai/transaction/errors';
|
|
30
|
-
import { resolveHeartbeatOptions } from '@abloatai/transaction/
|
|
31
|
+
import { resolveHeartbeatOptions } from '@abloatai/transaction/claims';
|
|
31
32
|
import { noopLogger } from '@abloatai/transaction/logger';
|
|
32
33
|
/** Readable target for the coordination trace: `documents:abc` / `documents:abc.title`. */
|
|
33
34
|
function claimLabel(type, id, field) {
|
|
@@ -47,7 +48,7 @@ export function createClaimStream(config, transport = null) {
|
|
|
47
48
|
// ── State: others' open claims, keyed by claimId ───────────────
|
|
48
49
|
const activeByClaimId = new Map();
|
|
49
50
|
let claimsSnapshot = Object.freeze([]);
|
|
50
|
-
// ── State: our own open
|
|
51
|
+
// ── State: our own open grants, keyed by the exact claim id ─────
|
|
51
52
|
const ownClaims = new Map();
|
|
52
53
|
// ── State: per-entity wait queues, from `claim_queue` frames ────
|
|
53
54
|
// Keyed `type:id`; the value is the FIFO line of queued claims. Powers
|
|
@@ -157,15 +158,18 @@ export function createClaimStream(config, transport = null) {
|
|
|
157
158
|
unsubs.push(t.subscribe('claim_rejected', (rejection) => {
|
|
158
159
|
if (!rejection.claimId)
|
|
159
160
|
return;
|
|
160
|
-
|
|
161
|
+
const own = ownClaims.get(rejection.claimId);
|
|
162
|
+
if (own) {
|
|
161
163
|
const tgt = rejection.target
|
|
162
164
|
? claimLabel(rejection.target.entityType, rejection.target.entityId, rejection.target.field)
|
|
163
165
|
: rejection.claimId;
|
|
164
166
|
logger.info(`claim: rejected ${tgt}${rejection.heldBy ? ` — held by ${rejection.heldBy}` : ''}`, { claimId: rejection.claimId, reason: rejection.reason });
|
|
165
167
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
own?.lifetime.end(new AbloClaimedError(rejection.message ?? `Claim ${rejection.claimId} was rejected.`, {
|
|
169
|
+
code: rejection.reason === 'conflict'
|
|
170
|
+
? 'claim_conflict'
|
|
171
|
+
: 'claim_lease_unavailable',
|
|
172
|
+
}));
|
|
169
173
|
ownClaims.delete(rejection.claimId);
|
|
170
174
|
// A holder on another server may have claimed before this client joined
|
|
171
175
|
// the row group, so its one-shot presence frame was missed. A conflict
|
|
@@ -193,12 +197,12 @@ export function createClaimStream(config, transport = null) {
|
|
|
193
197
|
// The frame is validated by the transport's dispatcher against the same
|
|
194
198
|
// schema this parameter's type comes from, so it arrives proven.
|
|
195
199
|
t.subscribe('claim_lost', (lost) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
const own = ownClaims.get(lost.claimId);
|
|
201
|
+
if (own) {
|
|
202
|
+
const c = own.claim;
|
|
203
|
+
logger.info(`claim: lost ${claimLabel(c.entityType, c.entityId, c.field)} (preempted or expired)`, { claimId: lost.claimId });
|
|
199
204
|
}
|
|
200
|
-
|
|
201
|
-
// no longer hold.
|
|
205
|
+
own?.lifetime.end(new AbloClaimedError(`Claim ${lost.claimId} was lost because it ${lost.reason === 'expired' ? 'expired' : 'was preempted'}. Re-acquire it before continuing.`, { code: 'claim_lost' }));
|
|
202
206
|
ownClaims.delete(lost.claimId);
|
|
203
207
|
for (const l of lostListeners) {
|
|
204
208
|
try {
|
|
@@ -260,17 +264,27 @@ export function createClaimStream(config, transport = null) {
|
|
|
260
264
|
});
|
|
261
265
|
return;
|
|
262
266
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
267
|
+
const own = ownClaims.get(ack.claimId);
|
|
268
|
+
const c = own?.claim;
|
|
269
|
+
const error = new AbloClaimedError(`The lease behind ${c ? claimLabel(c.entityType, c.entityId, c.field) : `claim ${ack.claimId}`} is no longer held — it expired or was granted onward while this participant was working. Re-acquire the claim and retry; a write attempted under the old lease is rejected by its \`readAt\` guard.`, { code: 'claim_lost' });
|
|
270
|
+
own?.lifetime.end(error);
|
|
271
|
+
ownClaims.delete(ack.claimId);
|
|
272
|
+
reject(error);
|
|
265
273
|
});
|
|
266
274
|
}));
|
|
267
|
-
// (3)
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
275
|
+
// (3) A granted handle belongs to this connection's exact grant. The server
|
|
276
|
+
// releases the participant's claims when its last socket leaves; trying
|
|
277
|
+
// to recreate them on `connected` silently swapped the lease beneath an
|
|
278
|
+
// old `data` / `readAt` / fenceToken tuple. End them instead. A caller
|
|
279
|
+
// reconnects, re-reads, and takes a fresh handle.
|
|
280
|
+
unsubs.push(t.subscribe('disconnected', () => {
|
|
281
|
+
const error = new AbloConnectionError('The connection holding this claim ended. Reconnect and acquire a fresh claim before continuing.');
|
|
282
|
+
for (const { lifetime } of ownClaims.values()) {
|
|
283
|
+
lifetime.end(error);
|
|
284
|
+
}
|
|
285
|
+
ownClaims.clear();
|
|
286
|
+
for (const claimId of [...pendingHeartbeats.keys()]) {
|
|
287
|
+
settleHeartbeat(claimId, ({ reject }) => reject(error));
|
|
274
288
|
}
|
|
275
289
|
}));
|
|
276
290
|
}
|
|
@@ -308,14 +322,14 @@ export function createClaimStream(config, transport = null) {
|
|
|
308
322
|
}
|
|
309
323
|
/**
|
|
310
324
|
* Send one heartbeat and await its ack. Rejects with
|
|
311
|
-
* {@link AbloConnectionError}
|
|
312
|
-
*
|
|
325
|
+
* {@link AbloConnectionError} when the socket is down or the ack times out,
|
|
326
|
+
* and with
|
|
313
327
|
* {@link AbloClaimedError} (definitive) when the server answers that the
|
|
314
328
|
* lease is no longer ours.
|
|
315
329
|
*/
|
|
316
330
|
function sendHeartbeat(claimId, claim, options) {
|
|
317
331
|
if (!attached?.isConnected()) {
|
|
318
|
-
return Promise.reject(new AbloConnectionError(`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down.
|
|
332
|
+
return Promise.reject(new AbloConnectionError(`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down. This connection's grant has ended; reconnect and acquire a fresh claim.`));
|
|
319
333
|
}
|
|
320
334
|
return new Promise((resolve, reject) => {
|
|
321
335
|
settleHeartbeat(claimId, ({ reject: rejectPrior }) => {
|
|
@@ -371,7 +385,8 @@ export function createClaimStream(config, transport = null) {
|
|
|
371
385
|
estimatedMs,
|
|
372
386
|
queue: args.queue,
|
|
373
387
|
};
|
|
374
|
-
|
|
388
|
+
const lifetime = createClaimLifetime();
|
|
389
|
+
ownClaims.set(claimId, { claim, lifetime });
|
|
375
390
|
sendBegin(claimId, claim);
|
|
376
391
|
// Coordination trace (info): the creator can see their human/agent claims.
|
|
377
392
|
logger.info(`claim: requesting ${claimLabel(claim.entityType, claim.entityId, claim.field)} for "${claim.description}"` +
|
|
@@ -381,11 +396,12 @@ export function createClaimStream(config, transport = null) {
|
|
|
381
396
|
if (revoked)
|
|
382
397
|
return;
|
|
383
398
|
revoked = true;
|
|
399
|
+
lifetime.end();
|
|
384
400
|
ownClaims.delete(claimId);
|
|
385
401
|
sendAbandon(claimId, claim);
|
|
386
402
|
logger.info(`claim: released ${claimLabel(claim.entityType, claim.entityId, claim.field)}`, { claimId });
|
|
387
403
|
};
|
|
388
|
-
return {
|
|
404
|
+
return bindClaimLifetime({
|
|
389
405
|
object: 'claim',
|
|
390
406
|
id: claimId,
|
|
391
407
|
status: 'active',
|
|
@@ -403,7 +419,7 @@ export function createClaimStream(config, transport = null) {
|
|
|
403
419
|
[Symbol.asyncDispose]: async () => {
|
|
404
420
|
revoke();
|
|
405
421
|
},
|
|
406
|
-
};
|
|
422
|
+
}, lifetime);
|
|
407
423
|
}
|
|
408
424
|
function resolveTarget(target) {
|
|
409
425
|
if (isTargetTuple(target))
|
|
@@ -471,6 +487,9 @@ export function createClaimStream(config, transport = null) {
|
|
|
471
487
|
reject(new AbloConnectionError('The claim stream was disposed while this heartbeat was awaiting its reply.'));
|
|
472
488
|
});
|
|
473
489
|
}
|
|
490
|
+
const error = new AbloConnectionError('The claim stream was disposed. Acquire a fresh claim before continuing.');
|
|
491
|
+
for (const { lifetime } of ownClaims.values())
|
|
492
|
+
lifetime.end(error);
|
|
474
493
|
listeners.clear();
|
|
475
494
|
rejectionListeners.clear();
|
|
476
495
|
lostListeners.clear();
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* as much as a browser does. This path re-exports it so existing importers
|
|
5
5
|
* stay unchanged.
|
|
6
6
|
*/
|
|
7
|
-
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/transport/
|
|
7
|
+
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, type CredentialRefreshOutcome, type CredentialRecoveryOutcome, type CredentialRefreshResult, type CredentialRefresher, type CredentialLifecycleContext, } from '@abloatai/transaction/transport/connection';
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* as much as a browser does. This path re-exports it so existing importers
|
|
5
5
|
* stay unchanged.
|
|
6
6
|
*/
|
|
7
|
-
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/transport/
|
|
7
|
+
export { DEFAULT_PREROLL_INTERVAL_MS, MIN_PREROLL_DELAY_MS, computePrerollDelayMs, CredentialLifecycle, } from '@abloatai/transaction/transport/connection';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JoinOptions } from '@abloatai/transaction/resources/modelOperations';
|
|
1
|
+
import type { JoinOptions } from '@abloatai/transaction/client/resources/modelOperations';
|
|
2
2
|
import type { SyncWebSocket } from './SyncWebSocket.js';
|
|
3
3
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
4
4
|
import type { Claim, Activity, ClaimTarget, ClaimStream, Peer, PresenceStream, PresenceTarget } from '@abloatai/transaction/types/streams';
|
|
@@ -19,6 +19,16 @@ export interface ServerSchemaModel {
|
|
|
19
19
|
readonly key: string;
|
|
20
20
|
/** Per-model content hash; absent on servers older than this check. */
|
|
21
21
|
readonly hash?: string;
|
|
22
|
+
readonly fields?: Readonly<Record<string, {
|
|
23
|
+
readonly type: string;
|
|
24
|
+
readonly isOptional: boolean;
|
|
25
|
+
}>>;
|
|
26
|
+
}
|
|
27
|
+
export interface SchemaFieldDrift {
|
|
28
|
+
readonly model: string;
|
|
29
|
+
readonly field: string;
|
|
30
|
+
readonly direction: 'client_only' | 'active_only' | 'changed';
|
|
31
|
+
readonly detail: string;
|
|
22
32
|
}
|
|
23
33
|
export type SchemaDriftFinding =
|
|
24
34
|
/** Every model this client declares exists server-side with matching content
|
|
@@ -38,13 +48,17 @@ export type SchemaDriftFinding =
|
|
|
38
48
|
readonly kind: 'changed';
|
|
39
49
|
readonly models: readonly string[];
|
|
40
50
|
readonly unpushed: readonly string[];
|
|
51
|
+
readonly fields?: readonly SchemaFieldDrift[];
|
|
41
52
|
}
|
|
42
53
|
/** The server surface carries no per-model hashes (older server) — the
|
|
43
54
|
* caller falls back to the whole-hash comparison. */
|
|
44
55
|
| {
|
|
45
56
|
readonly kind: 'unknown';
|
|
46
57
|
};
|
|
47
|
-
export declare function classifySchemaDrift(clientModels: Readonly<Record<string, string>>, serverModels: readonly ServerSchemaModel[]
|
|
58
|
+
export declare function classifySchemaDrift(clientModels: Readonly<Record<string, string>>, serverModels: readonly ServerSchemaModel[], clientShapes?: Readonly<Record<string, Readonly<Record<string, {
|
|
59
|
+
readonly type: string;
|
|
60
|
+
readonly isOptional: boolean;
|
|
61
|
+
}>>>>): SchemaDriftFinding;
|
|
48
62
|
/**
|
|
49
63
|
* The warning for a real, named divergence. Calm and specific: which models,
|
|
50
64
|
* what that means for this client, and the one next step. Never speaks about
|
|
@@ -14,22 +14,38 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Pure and transport-free; the BootstrapFetcher owns fetching the surface.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
import { reconcileClientToActive } from '@abloatai/transaction/schema';
|
|
18
|
+
export function classifySchemaDrift(clientModels, serverModels, clientShapes = {}) {
|
|
18
19
|
if (serverModels.length > 0 && serverModels.every((m) => !m.hash)) {
|
|
19
20
|
return { kind: 'unknown' };
|
|
20
21
|
}
|
|
21
22
|
const server = new Map(serverModels.map((m) => [m.key, m.hash]));
|
|
22
23
|
const unpushed = [];
|
|
23
24
|
const changed = [];
|
|
25
|
+
const fields = [];
|
|
24
26
|
for (const [key, hash] of Object.entries(clientModels)) {
|
|
25
27
|
const serverHash = server.get(key);
|
|
26
28
|
if (serverHash === undefined)
|
|
27
29
|
unpushed.push(key);
|
|
28
|
-
else if (serverHash !== hash)
|
|
30
|
+
else if (serverHash !== hash) {
|
|
29
31
|
changed.push(key);
|
|
32
|
+
const clientFields = clientShapes[key];
|
|
33
|
+
const activeFields = serverModels.find((model) => model.key === key)?.fields;
|
|
34
|
+
if (clientFields && activeFields)
|
|
35
|
+
for (const field of new Set([...Object.keys(clientFields), ...Object.keys(activeFields)])) {
|
|
36
|
+
const client = clientFields[field];
|
|
37
|
+
const active = activeFields[field];
|
|
38
|
+
if (!active)
|
|
39
|
+
fields.push({ model: key, field, direction: 'client_only', detail: 'present in this build but absent from the active schema' });
|
|
40
|
+
else if (!client)
|
|
41
|
+
fields.push({ model: key, field, direction: 'active_only', detail: 'present in the active schema but absent from this build' });
|
|
42
|
+
else if (client.type !== active.type || client.isOptional !== active.isOptional)
|
|
43
|
+
fields.push({ model: key, field, direction: 'changed', detail: `${client.type}${client.isOptional ? ' optional' : ' required'} in this build and ${active.type}${active.isOptional ? ' optional' : ' required'} in the active schema` });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
30
46
|
}
|
|
31
47
|
if (changed.length > 0)
|
|
32
|
-
return { kind: 'changed', models: changed, unpushed };
|
|
48
|
+
return { kind: 'changed', models: changed, unpushed, ...(fields.length ? { fields } : {}) };
|
|
33
49
|
if (unpushed.length > 0)
|
|
34
50
|
return { kind: 'unpushed', models: unpushed };
|
|
35
51
|
return { kind: 'aligned' };
|
|
@@ -40,14 +56,15 @@ export function classifySchemaDrift(clientModels, serverModels) {
|
|
|
40
56
|
* hashes — the point of the semantic check is that nobody has to compare hex.
|
|
41
57
|
*/
|
|
42
58
|
export function describeSchemaDrift(finding, serverLabel) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
|
|
59
|
+
const findings = finding.kind === 'unpushed'
|
|
60
|
+
? reconcileClientToActive([], finding.models, serverLabel)
|
|
61
|
+
: reconcileClientToActive(finding.models, finding.unpushed, serverLabel, finding.fields ?? []);
|
|
62
|
+
const changed = findings.filter(({ code }) => code === 'model_changed').map(({ model }) => model).filter(Boolean);
|
|
63
|
+
const unpushed = findings.filter(({ code }) => code === 'model_unpushed').map(({ model }) => model).filter(Boolean);
|
|
64
|
+
const summary = [
|
|
65
|
+
...findings.filter(({ field }) => field !== undefined).map(({ message }) => message),
|
|
66
|
+
...(changed.length ? [`Models ${changed.join(', ')} differ between this build and the active schema at ${serverLabel}.`] : []),
|
|
67
|
+
...(unpushed.length ? [`Models ${unpushed.join(', ')} are declared by this build but are not active at ${serverLabel}.`] : []),
|
|
68
|
+
];
|
|
69
|
+
return `Ablo: ${summary.join(' ')} ${findings.map(({ action }) => action).filter((value, index, all) => all.indexOf(value) === index).join(' ')}`;
|
|
53
70
|
}
|
|
@@ -20,10 +20,10 @@ export declare const ServerDeltaSchema: z.ZodObject<{
|
|
|
20
20
|
id: z.ZodNumber;
|
|
21
21
|
data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
|
|
22
22
|
actionType: z.ZodEnum<{
|
|
23
|
+
A: "A";
|
|
23
24
|
I: "I";
|
|
24
25
|
U: "U";
|
|
25
26
|
D: "D";
|
|
26
|
-
A: "A";
|
|
27
27
|
V: "V";
|
|
28
28
|
C: "C";
|
|
29
29
|
G: "G";
|
|
@@ -44,10 +44,10 @@ export declare const BootstrapResponseSchema: z.ZodObject<{
|
|
|
44
44
|
id: z.ZodNumber;
|
|
45
45
|
data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
|
|
46
46
|
actionType: z.ZodEnum<{
|
|
47
|
+
A: "A";
|
|
47
48
|
I: "I";
|
|
48
49
|
U: "U";
|
|
49
50
|
D: "D";
|
|
50
|
-
A: "A";
|
|
51
51
|
V: "V";
|
|
52
52
|
C: "C";
|
|
53
53
|
G: "G";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { globalRuntime } from "../context.js";
|
|
10
10
|
import { AbloValidationError } from "@abloatai/transaction/errors";
|
|
11
|
-
import { syncDeltaWireCoreSchema } from '@abloatai/transaction/
|
|
11
|
+
import { syncDeltaWireCoreSchema } from '@abloatai/transaction/observation';
|
|
12
12
|
// ─── Server Delta Schema ─────────────────────────────────────────────────────
|
|
13
13
|
/**
|
|
14
14
|
* A delta as it arrives in a bootstrap payload.
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* the transport object. This path re-exports it so existing importers stay
|
|
6
6
|
* unchanged.
|
|
7
7
|
*/
|
|
8
|
-
export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingClaim, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/
|
|
8
|
+
export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, type PendingCommit, type PendingClaim, type PendingSubscription, type WsInboundFrame, type WsSession, type WsFrameHandler, } from '@abloatai/transaction/transport/websocket';
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* the transport object. This path re-exports it so existing importers stay
|
|
6
6
|
* unchanged.
|
|
7
7
|
*/
|
|
8
|
-
export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, } from '@abloatai/transaction/transport/
|
|
8
|
+
export { isRecord, readWsInboundFrame, wsFrameHandlers, dispatchWsFrame, } from '@abloatai/transaction/transport/websocket';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DurableWriteStore } from '@abloatai/transaction/
|
|
2
|
-
import type { PendingWrite } from '@abloatai/transaction/
|
|
1
|
+
import type { DurableWriteStore } from '@abloatai/transaction/commit';
|
|
2
|
+
import type { PendingWrite } from '@abloatai/transaction/commit';
|
|
3
3
|
export interface CommitOutboxDatabase {
|
|
4
4
|
sealTransactionRecord(record: PendingWrite, consumedRecordIds: readonly string[]): Promise<unknown>;
|
|
5
5
|
getPersistedTransactions(): Promise<readonly unknown[]>;
|
|
@@ -17,10 +17,10 @@ import type { MutationPersistencePort } from '../../mutationPersistence.js';
|
|
|
17
17
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
18
18
|
import { type LogPositionPort } from '../../logPosition.js';
|
|
19
19
|
import type { WriteOptions } from '../../interfaces/index.js';
|
|
20
|
-
import type {
|
|
21
|
-
import { type CommitOperationResult } from '@abloatai/transaction/
|
|
20
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
21
|
+
import { type CommitOperationResult } from '@abloatai/transaction/commit';
|
|
22
22
|
import { type MutationInput, type QueuedMutation, type UserContext } from './commitPayload.js';
|
|
23
|
-
import { type CommitOutboxScope } from '@abloatai/transaction/
|
|
23
|
+
import { type CommitOutboxScope } from '@abloatai/transaction/commit';
|
|
24
24
|
import type { DurableWriteStore } from './durableWriteStore.js';
|
|
25
25
|
import { type LocalMutationPort } from './localMutation.js';
|
|
26
26
|
import { type CommitTransaction } from './commitLane.js';
|
|
@@ -151,7 +151,7 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
151
151
|
private get queueCoalescingContext();
|
|
152
152
|
private get batchProcessingContext();
|
|
153
153
|
private get failureHandlingContext();
|
|
154
|
-
private get
|
|
154
|
+
private get conflictResolutionContext();
|
|
155
155
|
private get processingSchedulerContext();
|
|
156
156
|
private get pendingDrainContext();
|
|
157
157
|
private get durableCommitRestoreContext();
|
|
@@ -163,18 +163,6 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
163
163
|
private computePriorityScore;
|
|
164
164
|
private ensureDerivedFields;
|
|
165
165
|
private entityKey;
|
|
166
|
-
/** Collision-safe receipt target identity across models sharing a row id. */
|
|
167
|
-
private receiptTargetKey;
|
|
168
|
-
/**
|
|
169
|
-
* Relates stale notifications back to write targets without assuming the
|
|
170
|
-
* server's canonical model name uses the same spelling as the public schema
|
|
171
|
-
* key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
|
|
172
|
-
* is the compatibility fallback. An ambiguous same-id cross-model mismatch
|
|
173
|
-
* is deliberately left unclassified, so it cannot falsely settle a queued
|
|
174
|
-
* write. A notification with no write-target id (or an explicit group) is a
|
|
175
|
-
* declared-read conflict and holds the whole batch.
|
|
176
|
-
*/
|
|
177
|
-
private classifyReceiptNotifications;
|
|
178
166
|
private resolveConfirmation;
|
|
179
167
|
private takeUnsentCreateForModel;
|
|
180
168
|
private cancelUnsentCreateForDelete;
|
|
@@ -185,7 +173,6 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
185
173
|
private config;
|
|
186
174
|
private executingCount;
|
|
187
175
|
private readonly localMutationPort;
|
|
188
|
-
private commitNotifications;
|
|
189
176
|
/** Zero-row targets returned on a successful atomic commit receipt. */
|
|
190
177
|
private commitMissingIds;
|
|
191
178
|
private readonly deltaConfirmation;
|
|
@@ -367,12 +354,10 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
367
354
|
/** Enqueues a pre-built atomic commit through the commit API coordinator. */
|
|
368
355
|
enqueueCommit(clientTxId: string, operations: CommitTransaction['operations'], options?: {
|
|
369
356
|
reads?: ReadDependency[] | null;
|
|
370
|
-
track?: TrackDependency[] | null;
|
|
371
357
|
}): Promise<void>;
|
|
372
358
|
private processCommitLane;
|
|
373
359
|
waitForCommitReceipt(clientTxId: string): Promise<{
|
|
374
360
|
lastSyncId: number;
|
|
375
|
-
notifications?: StaleNotification[];
|
|
376
361
|
missingIds?: string[];
|
|
377
362
|
operationResults?: CommitOperationResult[];
|
|
378
363
|
}>;
|
|
@@ -16,7 +16,7 @@ import { v4 as uuid } from 'uuid';
|
|
|
16
16
|
import { globalRuntime } from '../../context.js';
|
|
17
17
|
import { AbloConnectionError, AbloIdempotencyError, } from '@abloatai/transaction/errors';
|
|
18
18
|
import { LogPosition, } from '../../logPosition.js';
|
|
19
|
-
import {} from '@abloatai/transaction/
|
|
19
|
+
import {} from '@abloatai/transaction/commit';
|
|
20
20
|
import { computePriorityScore, normalizeModelKey, } from './commitPayload.js';
|
|
21
21
|
import { generateTransactionId, mergeMutationData, createDataFor, changesToInput, updateDataFor, previousDataFor, } from './mutationInput.js';
|
|
22
22
|
import { MutationStore } from './MutationStore.js';
|
|
@@ -24,8 +24,7 @@ import { entityKey, takeUnsentCreateForModel, findCreateBarrierForDelete, deferD
|
|
|
24
24
|
import { DeltaConfirmationTracker } from './deltaConfirmation.js';
|
|
25
25
|
import { deserializePersistedTransaction, isNonReplayablePersistedRow, pendingMutationRecordId, } from './replayValidation.js';
|
|
26
26
|
import { deserializeLegacyPendingMutation, loadPersistedTransactions, persistQueuedTransaction, removePersistedTransaction, settlePersistedFailure, } from './mutationPersistence.js';
|
|
27
|
-
import { createCommitEnvelopeMember, } from '@abloatai/transaction/
|
|
28
|
-
import { stableStringify } from '@abloatai/transaction/utils/json';
|
|
27
|
+
import { createCommitEnvelopeMember, } from '@abloatai/transaction/commit';
|
|
29
28
|
import { createLocalMutationPort, } from './localMutation.js';
|
|
30
29
|
import { dispatchCommitBounded, parseMutationCommitResult, persistDurableCommitAcceptance, removeDurableCommit, sealDurableCommit, } from './commitTransport.js';
|
|
31
30
|
import { processCommitLane, waitForCommitReceipt, } from './commitLane.js';
|
|
@@ -131,7 +130,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
131
130
|
retryBackoff: this.config.retryBackoff,
|
|
132
131
|
},
|
|
133
132
|
commitLane: this.commitLane,
|
|
134
|
-
commitNotifications: this.commitNotifications,
|
|
135
133
|
commitMissingIds: this.commitMissingIds,
|
|
136
134
|
commitProcessing: this.commitProcessing,
|
|
137
135
|
setCommitProcessing: (value) => { this.commitProcessing = value; },
|
|
@@ -141,7 +139,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
141
139
|
dispatchCommit: async (envelope) => this.parseMutationCommitResult(await this.dispatchCommitBounded(envelope.operations, {
|
|
142
140
|
idempotencyKey: envelope.idempotencyKey,
|
|
143
141
|
...(envelope.commitOptions.reads ? { reads: envelope.commitOptions.reads } : {}),
|
|
144
|
-
...(envelope.commitOptions.track ? { track: envelope.commitOptions.track } : {}),
|
|
145
142
|
})),
|
|
146
143
|
persistDurableCommitAcceptance: (envelope, result) => this.persistDurableCommitAcceptance(envelope, result),
|
|
147
144
|
removeDurableCommit: (idempotencyKey) => this.removeDurableCommit(idempotencyKey),
|
|
@@ -165,7 +162,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
165
162
|
get commitReceiptContext() {
|
|
166
163
|
return {
|
|
167
164
|
commitStore: this.commitStore,
|
|
168
|
-
commitNotifications: this.commitNotifications,
|
|
169
165
|
commitMissingIds: this.commitMissingIds,
|
|
170
166
|
replicationLagErrors: this.replicationLagErrors,
|
|
171
167
|
on: (event, listener) => this.on(event, listener),
|
|
@@ -248,7 +244,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
248
244
|
store: this.store,
|
|
249
245
|
enqueue: (transaction) => { this.enqueue(transaction); },
|
|
250
246
|
optimisticUpdates: this.localMutationPort.updates,
|
|
251
|
-
commitNotifications: this.commitNotifications,
|
|
252
247
|
commitMissingIds: this.commitMissingIds,
|
|
253
248
|
sourceMutationIdsFor: (batch) => this.sourceMutationIdsFor(batch),
|
|
254
249
|
dispatchCommitBounded: (...args) => this.dispatchCommitBounded(...args),
|
|
@@ -258,8 +253,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
258
253
|
assertEnvelopeInsideReplayWindow: (envelope) => { this.assertEnvelopeInsideReplayWindow(envelope); },
|
|
259
254
|
sealDurableCommit: (input) => this.sealDurableCommit(input),
|
|
260
255
|
noteAck: (syncId) => { this.noteAck(syncId); },
|
|
261
|
-
classifyReceiptNotifications: (operations, notifications) => this.classifyReceiptNotifications(operations, notifications),
|
|
262
|
-
receiptTargetKey: (modelName, modelId) => this.receiptTargetKey(modelName, modelId),
|
|
263
256
|
scheduleReplicationLagTimeout: (transactionId, clientTxId, correlationId) => { this.scheduleReplicationLagTimeout(transactionId, clientTxId, correlationId); },
|
|
264
257
|
scheduleDeltaConfirmationTimeout: (transaction, timeoutMs) => { this.scheduleDeltaConfirmationTimeout(transaction, timeoutMs); },
|
|
265
258
|
clearReplicationLagState: (transactionId) => { this.clearReplicationLagState(transactionId); },
|
|
@@ -288,7 +281,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
288
281
|
emit: (event, payload) => this.emit(event, payload),
|
|
289
282
|
};
|
|
290
283
|
}
|
|
291
|
-
get
|
|
284
|
+
get conflictResolutionContext() {
|
|
292
285
|
return {
|
|
293
286
|
config: this.config,
|
|
294
287
|
store: this.store,
|
|
@@ -409,59 +402,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
409
402
|
entityKey(modelName, modelId) {
|
|
410
403
|
return entityKey(modelName, modelId);
|
|
411
404
|
}
|
|
412
|
-
|
|
413
|
-
receiptTargetKey(modelName, modelId) {
|
|
414
|
-
return stableStringify([normalizeModelKey(modelName), modelId]);
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* Relates stale notifications back to write targets without assuming the
|
|
418
|
-
* server's canonical model name uses the same spelling as the public schema
|
|
419
|
-
* key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
|
|
420
|
-
* is the compatibility fallback. An ambiguous same-id cross-model mismatch
|
|
421
|
-
* is deliberately left unclassified, so it cannot falsely settle a queued
|
|
422
|
-
* write. A notification with no write-target id (or an explicit group) is a
|
|
423
|
-
* declared-read conflict and holds the whole batch.
|
|
424
|
-
*/
|
|
425
|
-
classifyReceiptNotifications(operations, notifications) {
|
|
426
|
-
const targets = operations.map((operation) => ({
|
|
427
|
-
id: operation.id,
|
|
428
|
-
key: this.receiptTargetKey(operation.model, operation.id),
|
|
429
|
-
}));
|
|
430
|
-
const heldTargets = new Set();
|
|
431
|
-
const notificationsByTarget = new Map();
|
|
432
|
-
let holdsEntireBatch = false;
|
|
433
|
-
for (const notification of notifications) {
|
|
434
|
-
// Scope is decided before any target matching. A group premise fires over
|
|
435
|
-
// the WHOLE batch by convention, and its `target` now names the row that
|
|
436
|
-
// actually moved — which may well be a row this batch is writing, so
|
|
437
|
-
// matching first would misread a batch-wide hold as a per-row one.
|
|
438
|
-
if (notification.scope === 'group') {
|
|
439
|
-
holdsEntireBatch = true;
|
|
440
|
-
continue;
|
|
441
|
-
}
|
|
442
|
-
const candidates = targets.filter((target) => target.id === notification.target.id);
|
|
443
|
-
const notificationKey = this.receiptTargetKey(notification.target.model, notification.target.id);
|
|
444
|
-
const exactTargets = candidates.filter((target) => target.key === notificationKey);
|
|
445
|
-
const candidateKeys = new Set((exactTargets.length > 0 ? exactTargets : candidates).map((target) => target.key));
|
|
446
|
-
if (candidates.length === 0) {
|
|
447
|
-
holdsEntireBatch = true;
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
if (candidateKeys.size !== 1) {
|
|
451
|
-
// Same id across multiple differently named models with no exact match:
|
|
452
|
-
// the id-only compatibility fallback is ambiguous. Await the echo.
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
const [targetKey] = candidateKeys;
|
|
456
|
-
if (!targetKey)
|
|
457
|
-
continue;
|
|
458
|
-
heldTargets.add(targetKey);
|
|
459
|
-
const targetNotifications = notificationsByTarget.get(targetKey) ?? [];
|
|
460
|
-
targetNotifications.push(notification);
|
|
461
|
-
notificationsByTarget.set(targetKey, targetNotifications);
|
|
462
|
-
}
|
|
463
|
-
return { holdsEntireBatch, heldTargets, notificationsByTarget };
|
|
464
|
-
}
|
|
405
|
+
/* Stale receipt classification was removed; stale premises now reject. */
|
|
465
406
|
resolveConfirmation(transaction) {
|
|
466
407
|
const resolver = this.confirmationResolvers.get(transaction.id);
|
|
467
408
|
if (!resolver)
|
|
@@ -543,11 +484,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
543
484
|
// Track executing transactions for backpressure
|
|
544
485
|
executingCount = 0;
|
|
545
486
|
localMutationPort;
|
|
546
|
-
// Stale-context notifications, keyed by transaction id. When the server
|
|
547
|
-
// accepts a commit but reports that an operation's premise had moved,
|
|
548
|
-
// the notification lands here from the commit acknowledgement and is drained
|
|
549
|
-
// by `waitForCommitReceipt`, so the receipt can carry it back to the caller.
|
|
550
|
-
commitNotifications = new Map();
|
|
551
487
|
/** Zero-row targets returned on a successful atomic commit receipt. */
|
|
552
488
|
commitMissingIds = new Map();
|
|
553
489
|
// Delta-confirmation tracking (ack watermark advance + the awaiting_delta
|
|
@@ -1266,7 +1202,7 @@ export class MutationQueue extends EventEmitter {
|
|
|
1266
1202
|
await handleFailure(this.failureHandlingContext, transaction, error);
|
|
1267
1203
|
}
|
|
1268
1204
|
async handleConflict(transaction, serverData) {
|
|
1269
|
-
await resolveConflict(this.
|
|
1205
|
+
await resolveConflict(this.conflictResolutionContext, transaction, serverData);
|
|
1270
1206
|
}
|
|
1271
1207
|
applyOptimisticCreate(model, transaction) {
|
|
1272
1208
|
this.localMutationPort.applyCreate(model, transaction);
|
|
@@ -1518,7 +1454,6 @@ export class MutationQueue extends EventEmitter {
|
|
|
1518
1454
|
this.recentDeltaCorrelations.clear();
|
|
1519
1455
|
this.commitLane = [];
|
|
1520
1456
|
this.commitStore.clear();
|
|
1521
|
-
this.commitNotifications.clear();
|
|
1522
1457
|
this.commitMissingIds.clear();
|
|
1523
1458
|
// Clear event listeners
|
|
1524
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;
|