@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
|
@@ -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';
|
|
@@ -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';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Model } from './Model.js';
|
|
2
|
+
import type { BootstrapData } from './sync/BootstrapFetcher.js';
|
|
3
|
+
import type { QueuedMutation } from './transactions/mutations/MutationQueue.js';
|
|
4
|
+
import type { CommitTransaction } from './transactions/mutations/commitLane.js';
|
|
5
|
+
export interface SyncObserver {
|
|
6
|
+
onSync?: (event: SyncEvent) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface SyncEvent {
|
|
9
|
+
type: 'create' | 'update' | 'delete' | 'archive' | 'rollback';
|
|
10
|
+
modelType: string;
|
|
11
|
+
model?: Model;
|
|
12
|
+
modelId?: string;
|
|
13
|
+
transactionType?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SyncState {
|
|
16
|
+
connectionState: 'connected' | 'disconnected' | 'connecting';
|
|
17
|
+
pendingMutations: number;
|
|
18
|
+
lastSyncAt?: Date;
|
|
19
|
+
error?: Error;
|
|
20
|
+
}
|
|
21
|
+
export interface RehydrationStats {
|
|
22
|
+
added: number;
|
|
23
|
+
updated: number;
|
|
24
|
+
removed: number;
|
|
25
|
+
skipped: number;
|
|
26
|
+
healed: number;
|
|
27
|
+
elapsedMs: number;
|
|
28
|
+
}
|
|
29
|
+
export type EventHandler = () => void;
|
|
30
|
+
/** The bootstrap fields applied to the local object pool. */
|
|
31
|
+
export type BootstrapSnapshot = Pick<BootstrapData, 'models' | 'failedModels'> & Partial<Pick<BootstrapData, 'lastSyncId'>>;
|
|
32
|
+
/** A completed queued mutation or explicit commit. */
|
|
33
|
+
export type CompletedTransaction = (Pick<QueuedMutation, 'id' | 'modelId' | 'syncIdNeededForCompletion'> & {
|
|
34
|
+
lastSyncId?: undefined;
|
|
35
|
+
operations?: undefined;
|
|
36
|
+
}) | (Pick<CommitTransaction, 'id' | 'lastSyncId' | 'operations'> & {
|
|
37
|
+
modelId?: undefined;
|
|
38
|
+
syncIdNeededForCompletion?: undefined;
|
|
39
|
+
});
|
|
40
|
+
/** Normalize an untyped server timestamp for last-write-wins comparison. */
|
|
41
|
+
export declare function toEpochMs(value: unknown): number;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Normalize an untyped server timestamp for last-write-wins comparison. */
|
|
2
|
+
export function toEpochMs(value) {
|
|
3
|
+
if (!value)
|
|
4
|
+
return 0;
|
|
5
|
+
if (value instanceof Date)
|
|
6
|
+
return value.getTime();
|
|
7
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
8
|
+
return new Date(value).getTime();
|
|
9
|
+
}
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DurableWriteStore } from '@abloatai/transaction/
|
|
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,9 +17,10 @@ import type { MutationPersistencePort } from '../../mutationPersistence.js';
|
|
|
17
17
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
18
18
|
import { type LogPositionPort } from '../../logPosition.js';
|
|
19
19
|
import type { WriteOptions } from '../../interfaces/index.js';
|
|
20
|
-
import type {
|
|
20
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
21
|
+
import { type CommitOperationResult } from '@abloatai/transaction/commit';
|
|
21
22
|
import { type MutationInput, type QueuedMutation, type UserContext } from './commitPayload.js';
|
|
22
|
-
import { type CommitOutboxScope } from '@abloatai/transaction/
|
|
23
|
+
import { type CommitOutboxScope } from '@abloatai/transaction/commit';
|
|
23
24
|
import type { DurableWriteStore } from './durableWriteStore.js';
|
|
24
25
|
import { type LocalMutationPort } from './localMutation.js';
|
|
25
26
|
import { type CommitTransaction } from './commitLane.js';
|
|
@@ -150,7 +151,7 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
150
151
|
private get queueCoalescingContext();
|
|
151
152
|
private get batchProcessingContext();
|
|
152
153
|
private get failureHandlingContext();
|
|
153
|
-
private get
|
|
154
|
+
private get conflictResolutionContext();
|
|
154
155
|
private get processingSchedulerContext();
|
|
155
156
|
private get pendingDrainContext();
|
|
156
157
|
private get durableCommitRestoreContext();
|
|
@@ -162,18 +163,6 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
162
163
|
private computePriorityScore;
|
|
163
164
|
private ensureDerivedFields;
|
|
164
165
|
private entityKey;
|
|
165
|
-
/** Collision-safe receipt target identity across models sharing a row id. */
|
|
166
|
-
private receiptTargetKey;
|
|
167
|
-
/**
|
|
168
|
-
* Relates stale notifications back to write targets without assuming the
|
|
169
|
-
* server's canonical model name uses the same spelling as the public schema
|
|
170
|
-
* key (`Item` versus `items`). Exact `(model,id)` wins; a globally unique id
|
|
171
|
-
* is the compatibility fallback. An ambiguous same-id cross-model mismatch
|
|
172
|
-
* is deliberately left unclassified, so it cannot falsely settle a queued
|
|
173
|
-
* write. A notification with no write-target id (or an explicit group) is a
|
|
174
|
-
* declared-read conflict and holds the whole batch.
|
|
175
|
-
*/
|
|
176
|
-
private classifyReceiptNotifications;
|
|
177
166
|
private resolveConfirmation;
|
|
178
167
|
private takeUnsentCreateForModel;
|
|
179
168
|
private cancelUnsentCreateForDelete;
|
|
@@ -184,7 +173,6 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
184
173
|
private config;
|
|
185
174
|
private executingCount;
|
|
186
175
|
private readonly localMutationPort;
|
|
187
|
-
private commitNotifications;
|
|
188
176
|
/** Zero-row targets returned on a successful atomic commit receipt. */
|
|
189
177
|
private commitMissingIds;
|
|
190
178
|
private readonly deltaConfirmation;
|
|
@@ -366,13 +354,12 @@ export declare class MutationQueue extends EventEmitter {
|
|
|
366
354
|
/** Enqueues a pre-built atomic commit through the commit API coordinator. */
|
|
367
355
|
enqueueCommit(clientTxId: string, operations: CommitTransaction['operations'], options?: {
|
|
368
356
|
reads?: ReadDependency[] | null;
|
|
369
|
-
track?: TrackDependency[] | null;
|
|
370
357
|
}): Promise<void>;
|
|
371
358
|
private processCommitLane;
|
|
372
359
|
waitForCommitReceipt(clientTxId: string): Promise<{
|
|
373
360
|
lastSyncId: number;
|
|
374
|
-
notifications?: StaleNotification[];
|
|
375
361
|
missingIds?: string[];
|
|
362
|
+
operationResults?: CommitOperationResult[];
|
|
376
363
|
}>;
|
|
377
364
|
private isReorderPayload;
|
|
378
365
|
private isPermanentError;
|