@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
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import type {
|
|
26
26
|
WsTransport,
|
|
27
27
|
PresenceUpdate,
|
|
28
|
-
} from '@abloatai/transaction/transport/
|
|
28
|
+
} from '@abloatai/transaction/transport/websocket';
|
|
29
29
|
import type {
|
|
30
30
|
ClaimOptions,
|
|
31
31
|
ClaimTarget,
|
|
@@ -52,10 +52,15 @@ import {
|
|
|
52
52
|
streamTarget,
|
|
53
53
|
wireTarget,
|
|
54
54
|
type ClaimTargetDetails,
|
|
55
|
-
} from '@abloatai/transaction/
|
|
56
|
-
import {
|
|
55
|
+
} from '@abloatai/transaction/claims';
|
|
56
|
+
import {
|
|
57
|
+
bindClaimLifetime,
|
|
58
|
+
createClaimLifetime,
|
|
59
|
+
type ClaimLifetime,
|
|
60
|
+
} from '@abloatai/transaction/claims/lifetime';
|
|
61
|
+
import { declaredMeta } from '@abloatai/transaction/claims';
|
|
57
62
|
import { AbloClaimedError, AbloConnectionError } from '@abloatai/transaction/errors';
|
|
58
|
-
import { resolveHeartbeatOptions } from '@abloatai/transaction/
|
|
63
|
+
import { resolveHeartbeatOptions } from '@abloatai/transaction/claims';
|
|
59
64
|
import { noopLogger, type Logger } from '@abloatai/transaction/logger';
|
|
60
65
|
|
|
61
66
|
/**
|
|
@@ -114,13 +119,11 @@ export interface AttachableClaimStream extends ClaimStream {
|
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
/**
|
|
117
|
-
* What this participant needs to remember about
|
|
118
|
-
* re-announce it after a reconnect.
|
|
122
|
+
* What this participant needs to remember about one open grant.
|
|
119
123
|
*
|
|
120
124
|
* The sub-entity locator is taken from {@link ClaimTargetDetails} rather than
|
|
121
|
-
* listed again: a member this record forgot would be a member
|
|
122
|
-
*
|
|
123
|
-
* believes it has.
|
|
125
|
+
* listed again: a member this record forgot would be a member release or loss
|
|
126
|
+
* bookkeeping silently narrowed relative to the handle the caller holds.
|
|
124
127
|
*/
|
|
125
128
|
type OwnClaim = ClaimTargetDetails & {
|
|
126
129
|
readonly entityType: string;
|
|
@@ -133,6 +136,11 @@ type OwnClaim = ClaimTargetDetails & {
|
|
|
133
136
|
readonly queue?: boolean;
|
|
134
137
|
};
|
|
135
138
|
|
|
139
|
+
interface OwnGrant {
|
|
140
|
+
readonly claim: OwnClaim;
|
|
141
|
+
readonly lifetime: ClaimLifetime;
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
export function createClaimStream(
|
|
137
145
|
config: ClaimStreamConfig,
|
|
138
146
|
transport: ClaimTransport | null = null,
|
|
@@ -146,8 +154,8 @@ export function createClaimStream(
|
|
|
146
154
|
const activeByClaimId = new Map<string, Claim>();
|
|
147
155
|
let claimsSnapshot: readonly Claim[] = Object.freeze([]);
|
|
148
156
|
|
|
149
|
-
// ── State: our own open
|
|
150
|
-
const ownClaims = new Map<string,
|
|
157
|
+
// ── State: our own open grants, keyed by the exact claim id ─────
|
|
158
|
+
const ownClaims = new Map<string, OwnGrant>();
|
|
151
159
|
|
|
152
160
|
// ── State: per-entity wait queues, from `claim_queue` frames ────
|
|
153
161
|
// Keyed `type:id`; the value is the FIFO line of queued claims. Powers
|
|
@@ -290,7 +298,8 @@ export function createClaimStream(
|
|
|
290
298
|
unsubs.push(
|
|
291
299
|
t.subscribe('claim_rejected', (rejection) => {
|
|
292
300
|
if (!rejection.claimId) return;
|
|
293
|
-
|
|
301
|
+
const own = ownClaims.get(rejection.claimId);
|
|
302
|
+
if (own) {
|
|
294
303
|
const tgt = rejection.target
|
|
295
304
|
? claimLabel(rejection.target.entityType, rejection.target.entityId, rejection.target.field)
|
|
296
305
|
: rejection.claimId;
|
|
@@ -299,9 +308,17 @@ export function createClaimStream(
|
|
|
299
308
|
{ claimId: rejection.claimId, reason: rejection.reason },
|
|
300
309
|
);
|
|
301
310
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
own?.lifetime.end(
|
|
312
|
+
new AbloClaimedError(
|
|
313
|
+
rejection.message ?? `Claim ${rejection.claimId} was rejected.`,
|
|
314
|
+
{
|
|
315
|
+
code:
|
|
316
|
+
rejection.reason === 'conflict'
|
|
317
|
+
? 'claim_conflict'
|
|
318
|
+
: 'claim_lease_unavailable',
|
|
319
|
+
},
|
|
320
|
+
),
|
|
321
|
+
);
|
|
305
322
|
ownClaims.delete(rejection.claimId);
|
|
306
323
|
// A holder on another server may have claimed before this client joined
|
|
307
324
|
// the row group, so its one-shot presence frame was missed. A conflict
|
|
@@ -336,15 +353,20 @@ export function createClaimStream(
|
|
|
336
353
|
// The frame is validated by the transport's dispatcher against the same
|
|
337
354
|
// schema this parameter's type comes from, so it arrives proven.
|
|
338
355
|
t.subscribe('claim_lost', (lost) => {
|
|
339
|
-
|
|
340
|
-
|
|
356
|
+
const own = ownClaims.get(lost.claimId);
|
|
357
|
+
if (own) {
|
|
358
|
+
const c = own.claim;
|
|
341
359
|
logger.info(
|
|
342
|
-
`claim: lost ${
|
|
360
|
+
`claim: lost ${claimLabel(c.entityType, c.entityId, c.field)} (preempted or expired)`,
|
|
343
361
|
{ claimId: lost.claimId },
|
|
344
362
|
);
|
|
345
363
|
}
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
own?.lifetime.end(
|
|
365
|
+
new AbloClaimedError(
|
|
366
|
+
`Claim ${lost.claimId} was lost because it ${lost.reason === 'expired' ? 'expired' : 'was preempted'}. Re-acquire it before continuing.`,
|
|
367
|
+
{ code: 'claim_lost' },
|
|
368
|
+
),
|
|
369
|
+
);
|
|
348
370
|
ownClaims.delete(lost.claimId);
|
|
349
371
|
for (const l of lostListeners) {
|
|
350
372
|
try {
|
|
@@ -418,25 +440,35 @@ export function createClaimStream(
|
|
|
418
440
|
});
|
|
419
441
|
return;
|
|
420
442
|
}
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
),
|
|
443
|
+
const own = ownClaims.get(ack.claimId);
|
|
444
|
+
const c = own?.claim;
|
|
445
|
+
const error = new AbloClaimedError(
|
|
446
|
+
`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.`,
|
|
447
|
+
{ code: 'claim_lost' },
|
|
427
448
|
);
|
|
449
|
+
own?.lifetime.end(error);
|
|
450
|
+
ownClaims.delete(ack.claimId);
|
|
451
|
+
reject(error);
|
|
428
452
|
});
|
|
429
453
|
}),
|
|
430
454
|
);
|
|
431
455
|
|
|
432
|
-
// (3)
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
456
|
+
// (3) A granted handle belongs to this connection's exact grant. The server
|
|
457
|
+
// releases the participant's claims when its last socket leaves; trying
|
|
458
|
+
// to recreate them on `connected` silently swapped the lease beneath an
|
|
459
|
+
// old `data` / `readAt` / fenceToken tuple. End them instead. A caller
|
|
460
|
+
// reconnects, re-reads, and takes a fresh handle.
|
|
436
461
|
unsubs.push(
|
|
437
|
-
t.subscribe('
|
|
438
|
-
|
|
439
|
-
|
|
462
|
+
t.subscribe('disconnected', () => {
|
|
463
|
+
const error = new AbloConnectionError(
|
|
464
|
+
'The connection holding this claim ended. Reconnect and acquire a fresh claim before continuing.',
|
|
465
|
+
);
|
|
466
|
+
for (const { lifetime } of ownClaims.values()) {
|
|
467
|
+
lifetime.end(error);
|
|
468
|
+
}
|
|
469
|
+
ownClaims.clear();
|
|
470
|
+
for (const claimId of [...pendingHeartbeats.keys()]) {
|
|
471
|
+
settleHeartbeat(claimId, ({ reject }) => reject(error));
|
|
440
472
|
}
|
|
441
473
|
}),
|
|
442
474
|
);
|
|
@@ -480,8 +512,8 @@ export function createClaimStream(
|
|
|
480
512
|
|
|
481
513
|
/**
|
|
482
514
|
* Send one heartbeat and await its ack. Rejects with
|
|
483
|
-
* {@link AbloConnectionError}
|
|
484
|
-
*
|
|
515
|
+
* {@link AbloConnectionError} when the socket is down or the ack times out,
|
|
516
|
+
* and with
|
|
485
517
|
* {@link AbloClaimedError} (definitive) when the server answers that the
|
|
486
518
|
* lease is no longer ours.
|
|
487
519
|
*/
|
|
@@ -493,7 +525,7 @@ export function createClaimStream(
|
|
|
493
525
|
if (!attached?.isConnected()) {
|
|
494
526
|
return Promise.reject(
|
|
495
527
|
new AbloConnectionError(
|
|
496
|
-
`The heartbeat for ${claimLabel(claim.entityType, claim.entityId, claim.field)} was skipped because the connection is down.
|
|
528
|
+
`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.`,
|
|
497
529
|
),
|
|
498
530
|
);
|
|
499
531
|
}
|
|
@@ -567,7 +599,8 @@ export function createClaimStream(
|
|
|
567
599
|
estimatedMs,
|
|
568
600
|
queue: args.queue,
|
|
569
601
|
};
|
|
570
|
-
|
|
602
|
+
const lifetime = createClaimLifetime();
|
|
603
|
+
ownClaims.set(claimId, { claim, lifetime });
|
|
571
604
|
sendBegin(claimId, claim);
|
|
572
605
|
// Coordination trace (info): the creator can see their human/agent claims.
|
|
573
606
|
logger.info(
|
|
@@ -580,6 +613,7 @@ export function createClaimStream(
|
|
|
580
613
|
const revoke = () => {
|
|
581
614
|
if (revoked) return;
|
|
582
615
|
revoked = true;
|
|
616
|
+
lifetime.end();
|
|
583
617
|
ownClaims.delete(claimId);
|
|
584
618
|
sendAbandon(claimId, claim);
|
|
585
619
|
logger.info(
|
|
@@ -588,7 +622,7 @@ export function createClaimStream(
|
|
|
588
622
|
);
|
|
589
623
|
};
|
|
590
624
|
|
|
591
|
-
return {
|
|
625
|
+
return bindClaimLifetime({
|
|
592
626
|
object: 'claim',
|
|
593
627
|
id: claimId,
|
|
594
628
|
status: 'active',
|
|
@@ -607,7 +641,7 @@ export function createClaimStream(
|
|
|
607
641
|
[Symbol.asyncDispose]: async () => {
|
|
608
642
|
revoke();
|
|
609
643
|
},
|
|
610
|
-
};
|
|
644
|
+
}, lifetime);
|
|
611
645
|
}
|
|
612
646
|
|
|
613
647
|
function resolveTarget(target: PresenceTarget): ClaimTarget {
|
|
@@ -686,6 +720,10 @@ export function createClaimStream(
|
|
|
686
720
|
);
|
|
687
721
|
});
|
|
688
722
|
}
|
|
723
|
+
const error = new AbloConnectionError(
|
|
724
|
+
'The claim stream was disposed. Acquire a fresh claim before continuing.',
|
|
725
|
+
);
|
|
726
|
+
for (const { lifetime } of ownClaims.values()) lifetime.end(error);
|
|
689
727
|
listeners.clear();
|
|
690
728
|
rejectionListeners.clear();
|
|
691
729
|
lostListeners.clear();
|
|
@@ -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, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
4
4
|
import { scopeKindOf, type ModelDef } from '@abloatai/transaction/schema/model';
|
|
@@ -10,7 +10,7 @@ import { z } from 'zod';
|
|
|
10
10
|
import { globalRuntime } from "../context.js";
|
|
11
11
|
import type { RuntimeContext } from "../RuntimeContext.js";
|
|
12
12
|
import { AbloValidationError } from "@abloatai/transaction/errors";
|
|
13
|
-
import { syncDeltaWireCoreSchema } from '@abloatai/transaction/
|
|
13
|
+
import { syncDeltaWireCoreSchema } from '@abloatai/transaction/observation';
|
|
14
14
|
|
|
15
15
|
// ─── Server Delta Schema ─────────────────────────────────────────────────────
|
|
16
16
|
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
|
|
6
|
+
export interface SyncObserver {
|
|
7
|
+
onSync?: (event: SyncEvent) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SyncEvent {
|
|
11
|
+
type: 'create' | 'update' | 'delete' | 'archive' | 'rollback';
|
|
12
|
+
modelType: string;
|
|
13
|
+
model?: Model;
|
|
14
|
+
modelId?: string;
|
|
15
|
+
transactionType?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SyncState {
|
|
19
|
+
connectionState: 'connected' | 'disconnected' | 'connecting';
|
|
20
|
+
pendingMutations: number;
|
|
21
|
+
lastSyncAt?: Date;
|
|
22
|
+
error?: Error;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RehydrationStats {
|
|
26
|
+
added: number;
|
|
27
|
+
updated: number;
|
|
28
|
+
removed: number;
|
|
29
|
+
skipped: number;
|
|
30
|
+
healed: number;
|
|
31
|
+
elapsedMs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type EventHandler = () => void;
|
|
35
|
+
|
|
36
|
+
/** The bootstrap fields applied to the local object pool. */
|
|
37
|
+
export type BootstrapSnapshot = Pick<BootstrapData, 'models' | 'failedModels'> &
|
|
38
|
+
Partial<Pick<BootstrapData, 'lastSyncId'>>;
|
|
39
|
+
|
|
40
|
+
/** A completed queued mutation or explicit commit. */
|
|
41
|
+
export type CompletedTransaction =
|
|
42
|
+
| (Pick<QueuedMutation, 'id' | 'modelId' | 'syncIdNeededForCompletion'> & {
|
|
43
|
+
lastSyncId?: undefined;
|
|
44
|
+
operations?: undefined;
|
|
45
|
+
})
|
|
46
|
+
| (Pick<CommitTransaction, 'id' | 'lastSyncId' | 'operations'> & {
|
|
47
|
+
modelId?: undefined;
|
|
48
|
+
syncIdNeededForCompletion?: undefined;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** Normalize an untyped server timestamp for last-write-wins comparison. */
|
|
52
|
+
export function toEpochMs(value: unknown): number {
|
|
53
|
+
if (!value) return 0;
|
|
54
|
+
if (value instanceof Date) return value.getTime();
|
|
55
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
56
|
+
return new Date(value).getTime();
|
|
57
|
+
}
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
DurableWriteStore,
|
|
3
|
-
} from '@abloatai/transaction/
|
|
4
|
-
import type { PendingWrite } from '@abloatai/transaction/
|
|
3
|
+
} from '@abloatai/transaction/commit';
|
|
4
|
+
import type { PendingWrite } from '@abloatai/transaction/commit';
|
|
5
5
|
|
|
6
6
|
export interface CommitOutboxDatabase {
|
|
7
7
|
sealTransactionRecord(
|