@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
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
8
8
|
import type { RuntimeConfig, Logger, MutationExecutor, ObservabilityProvider, Analytics, SessionErrorDetector, OnlineStatusProvider } from '../interfaces/index.js';
|
|
9
9
|
import type { AbloPersistence } from '../persistence.js';
|
|
10
|
-
import type { DurableWriteStore, DurableWritesConfig } from '@abloatai/transaction/
|
|
11
|
-
import type { CommitOutboxScope } from '@abloatai/transaction/
|
|
10
|
+
import type { DurableWriteStore, DurableWritesConfig } from '@abloatai/transaction/commit';
|
|
11
|
+
import type { CommitOutboxScope } from '@abloatai/transaction/commit';
|
|
12
12
|
/**
|
|
13
13
|
* An async function that resolves an apiKey at request time. Use it for credential
|
|
14
14
|
* rotation — read from a vault, refresh from session storage, or pull from an
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* design step (docs/plans/package-split.md).
|
|
13
13
|
*/
|
|
14
14
|
import { omittedModelError } from '@abloatai/transaction/schema/select';
|
|
15
|
-
import { durableCommitOperationSchema, } from '@abloatai/transaction/
|
|
15
|
+
import { durableCommitOperationSchema, } from '@abloatai/transaction/commit';
|
|
16
16
|
import { AbloAuthenticationError, AbloConnectionError, AbloValidationError, claimedError } from '@abloatai/transaction/errors';
|
|
17
17
|
import { batchFence, claimIdFor, fenceTokenFor, modelTarget, streamTarget, subTarget, } from '@abloatai/transaction/coordination';
|
|
18
18
|
import { validateAbloOptions } from './validateAbloOptions.js';
|
|
@@ -21,16 +21,17 @@ import { revokeCapability, rotateCapability, } from '@abloatai/transaction/auth/
|
|
|
21
21
|
import { modelWireNames } from '@abloatai/transaction/auth/capability';
|
|
22
22
|
import { startStoreLifecycle } from './storeLifecycle.js';
|
|
23
23
|
import { createClaimStream } from '../sync/createClaimStream.js';
|
|
24
|
-
import { awaitClaimGrant } from '@abloatai/transaction/
|
|
25
|
-
import {
|
|
24
|
+
import { awaitClaimGrant } from '@abloatai/transaction/claims';
|
|
25
|
+
import { bindClaimLifetime, claimLifetimeOf, } from '@abloatai/transaction/claims/lifetime';
|
|
26
26
|
import { createParticipantManager } from '../sync/participants.js';
|
|
27
27
|
import { resolveApiKeyValue, resolveBootstrapBaseUrl } from '@abloatai/transaction/auth/apiKey';
|
|
28
|
-
import { claimAttemptFailure, emitClaimStatus, } from '@abloatai/transaction/resources/modelOperations';
|
|
29
|
-
import {
|
|
30
|
-
import { assertWriteOptions } from '@abloatai/transaction/resources/writeOptionsSchema';
|
|
28
|
+
import { claimAttemptFailure, emitClaimStatus, } from '@abloatai/transaction/client/resources/modelOperations';
|
|
29
|
+
import { createModelOperations } from './createModelOperations.js';
|
|
30
|
+
import { assertWriteOptions } from '@abloatai/transaction/client/resources/writeOptionsSchema';
|
|
31
31
|
import { modelReadResponseSchema, commitRecordSchema, commitRecordListSchema, commitRecordWhereSchema, } from '@abloatai/transaction/wire';
|
|
32
|
-
import { translateHttpError } from '@abloatai/transaction/errors';
|
|
33
|
-
import { kReadEvidence } from '@abloatai/transaction/internal/read-set';
|
|
32
|
+
import { translateHttpError, } from '@abloatai/transaction/errors';
|
|
33
|
+
import { kReadEvidence, prepareReadSet, } from '@abloatai/transaction/internal/read-set';
|
|
34
|
+
import { contextOnChange } from '../sync/contextOnChange.js';
|
|
34
35
|
export function buildReactiveEngine(inputs) {
|
|
35
36
|
const { options, internalOptions, url, logger, configuredApiKey, configuredAuthToken, credentialResolver, authCredentials, transport, participantId, kind, presence, cluster, createSibling, } = inputs;
|
|
36
37
|
const schema = options.schema;
|
|
@@ -186,7 +187,6 @@ export function buildReactiveEngine(inputs) {
|
|
|
186
187
|
transactionId: op.transactionId ?? undefined,
|
|
187
188
|
claimId: op.claimId ?? claimIdFor(claim?.target, claim?.id, op.model, op.id ?? null) ?? undefined,
|
|
188
189
|
readAt: op.readAt ?? defaults.readAt ?? undefined,
|
|
189
|
-
onStale: op.onStale ?? defaults.onStale ?? undefined,
|
|
190
190
|
fenceToken: op.fenceToken ?? fenceTokenFor(fence, op.model, op.id ?? null) ?? undefined,
|
|
191
191
|
});
|
|
192
192
|
}
|
|
@@ -292,7 +292,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
292
292
|
// the one `awaitClaimGrant` read there; retain the handle fallback for
|
|
293
293
|
// wire-compatible transports that already stamped it locally.
|
|
294
294
|
const resolvedFenceToken = fenceToken ?? claim.fenceToken;
|
|
295
|
-
|
|
295
|
+
const wrapped = {
|
|
296
296
|
object: 'claim',
|
|
297
297
|
id: claim.id,
|
|
298
298
|
description: claim.description,
|
|
@@ -308,6 +308,8 @@ export function buildReactiveEngine(inputs) {
|
|
|
308
308
|
heartbeat: claim.heartbeat,
|
|
309
309
|
[Symbol.asyncDispose]: release,
|
|
310
310
|
};
|
|
311
|
+
const lifetime = claimLifetimeOf(claim);
|
|
312
|
+
return lifetime ? bindClaimLifetime(wrapped, lifetime) : wrapped;
|
|
311
313
|
}
|
|
312
314
|
const publicClaims = Object.assign(claimStream, {
|
|
313
315
|
async create(claimOptions) {
|
|
@@ -406,22 +408,17 @@ export function buildReactiveEngine(inputs) {
|
|
|
406
408
|
const modelProxies = {};
|
|
407
409
|
for (const [schemaKey, modelDef] of Object.entries(schema.models)) {
|
|
408
410
|
const registeredModelName = modelDef.typename ?? schemaKey;
|
|
409
|
-
modelProxies[schemaKey] =
|
|
411
|
+
modelProxies[schemaKey] = createModelOperations(schemaKey, registeredModelName, objectPool, syncClient, modelRegistry, hydration, {
|
|
410
412
|
createClaim: (claimOptions) => publicClaims.create(claimOptions),
|
|
413
|
+
// Lazily referenced: `commits` is declared below this loop, and this
|
|
414
|
+
// only runs when someone actually writes a batch.
|
|
415
|
+
commitBatch: (commitOptions) => commits.create(commitOptions),
|
|
411
416
|
readPoint,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
// watermark for our own writes — see logPosition.ts).
|
|
418
|
-
// Stamping a bare stream cursor made a claim taken right after
|
|
419
|
-
// an ack-confirmed write stale against that write's own delta.
|
|
420
|
-
// The socket/store cursors are persistence-gated and therefore
|
|
421
|
-
// never ahead of `applied` — no extra max() needed here.
|
|
422
|
-
getLastSyncId: () => syncClient.position.readFloor,
|
|
423
|
-
entities: { [modelKey]: id },
|
|
424
|
-
}),
|
|
417
|
+
// A claim needs the post-read watermark, not the legacy snapshot
|
|
418
|
+
// object. `readFloor` is max(applied, acked-own-write), so a claim
|
|
419
|
+
// taken immediately after its own confirmed mutation is not stale
|
|
420
|
+
// against an echo that has not materialised locally yet.
|
|
421
|
+
currentReadAt: () => syncClient.position.readFloor,
|
|
425
422
|
queue: (target) => publicClaims.queueFor(streamTarget(target)),
|
|
426
423
|
reorder: (target, order) => { publicClaims.reorder(streamTarget(target), order); },
|
|
427
424
|
// One row can have several holders — sub-row claims on disjoint
|
|
@@ -466,23 +463,24 @@ export function buildReactiveEngine(inputs) {
|
|
|
466
463
|
const commits = {
|
|
467
464
|
async create(commitOptions) {
|
|
468
465
|
await ready();
|
|
466
|
+
const prepared = prepareReadSet(cluster.readSetContext, syncClient, commitOptions.readAt, commitOptions.idempotencyKey, commitOptions.reads);
|
|
469
467
|
// Same runtime contract as the per-model writes — one schema.
|
|
470
468
|
assertWriteOptions({
|
|
471
|
-
idempotencyKey:
|
|
472
|
-
readAt:
|
|
473
|
-
onStale: commitOptions.onStale,
|
|
469
|
+
idempotencyKey: prepared.idempotencyKey,
|
|
470
|
+
readAt: prepared.readAt,
|
|
474
471
|
wait: commitOptions.wait,
|
|
475
472
|
claim: commitOptions.claim,
|
|
473
|
+
reads: prepared.reads,
|
|
476
474
|
}, 'commits.create');
|
|
477
|
-
const clientTxId = createClientTxId(
|
|
475
|
+
const clientTxId = createClientTxId(prepared.idempotencyKey);
|
|
478
476
|
// A claim handle supplies the batch stale-guard defaults — same
|
|
479
477
|
// semantics as `ablo.<model>.update({ id, data, claim })`, so the
|
|
480
478
|
// two write doors speak one claim vocabulary. Explicit options win.
|
|
481
479
|
const claim = commitOptions.claim ?? null;
|
|
482
480
|
const operations = normalizeCommitOperations({
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
481
|
+
operations: commitOptions.operations,
|
|
482
|
+
...(commitOptions.claim !== undefined ? { claim: commitOptions.claim } : {}),
|
|
483
|
+
readAt: prepared.readAt ?? claim?.readAt ?? null,
|
|
486
484
|
}, batchFence(claim?.target, claim?.fenceToken));
|
|
487
485
|
const wait = commitOptions.wait ?? 'confirmed';
|
|
488
486
|
// Route through the MutationQueue's commit lane so the call
|
|
@@ -496,19 +494,18 @@ export function buildReactiveEngine(inputs) {
|
|
|
496
494
|
// no need to leak an accessor through BaseSyncedStore.
|
|
497
495
|
const queue = syncClient.getMutationQueue();
|
|
498
496
|
await queue.enqueueCommit(clientTxId, operations, {
|
|
499
|
-
...(
|
|
500
|
-
...(commitOptions.track ? { track: [...commitOptions.track] } : {}),
|
|
497
|
+
...(prepared.reads ? { reads: [...prepared.reads] } : {}),
|
|
501
498
|
});
|
|
502
499
|
if (wait === 'queued') {
|
|
503
500
|
return { id: clientTxId, status: 'queued' };
|
|
504
501
|
}
|
|
505
|
-
const { lastSyncId,
|
|
502
|
+
const { lastSyncId, missingIds, operationResults } = await queue.waitForCommitReceipt(clientTxId);
|
|
506
503
|
return {
|
|
507
504
|
id: clientTxId,
|
|
508
505
|
status: 'confirmed',
|
|
509
506
|
lastSyncId,
|
|
510
|
-
...(notifications && notifications.length > 0 ? { notifications } : {}),
|
|
511
507
|
...(missingIds && missingIds.length > 0 ? { missingIds } : {}),
|
|
508
|
+
...(operationResults && operationResults.length > 0 ? { operationResults } : {}),
|
|
512
509
|
};
|
|
513
510
|
},
|
|
514
511
|
async get({ id }) {
|
|
@@ -677,6 +674,7 @@ export function buildReactiveEngine(inputs) {
|
|
|
677
674
|
const sessionParams = {
|
|
678
675
|
agent: { id },
|
|
679
676
|
can: params.can,
|
|
677
|
+
...(params.onBehalfOf ? { onBehalfOf: params.onBehalfOf } : {}),
|
|
680
678
|
...(params.syncGroups ? { syncGroups: params.syncGroups } : {}),
|
|
681
679
|
...(params.ttlSeconds !== undefined ? { ttlSeconds: params.ttlSeconds } : {}),
|
|
682
680
|
...(userMeta ? { userMeta } : {}),
|
|
@@ -764,18 +762,13 @@ export function buildReactiveEngine(inputs) {
|
|
|
764
762
|
/** Claim livestream — same socket. Stable reference. */
|
|
765
763
|
claims: publicClaims,
|
|
766
764
|
commits,
|
|
767
|
-
/** Context-staleness snapshot — see `engine.snapshot(...)` JSDoc. */
|
|
768
|
-
snapshot(entities) {
|
|
769
|
-
return createSnapshot({
|
|
770
|
-
pool: objectPool,
|
|
771
|
-
transport,
|
|
772
|
-
getLastSyncId: () => transport.getLastSyncId(),
|
|
773
|
-
entities,
|
|
774
|
-
});
|
|
775
|
-
},
|
|
776
765
|
};
|
|
777
766
|
Object.defineProperty(engine, kReadEvidence, {
|
|
778
|
-
value: {
|
|
767
|
+
value: {
|
|
768
|
+
context: cluster.readSetContext,
|
|
769
|
+
client: syncClient,
|
|
770
|
+
onChange: (reads, listener) => contextOnChange(syncClient, objectPool, reads, listener),
|
|
771
|
+
},
|
|
779
772
|
enumerable: false,
|
|
780
773
|
});
|
|
781
774
|
// A model the schema projection left out answers with an error naming the
|
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* that stay here — `ModelOperations` chief among them, because it returns the
|
|
8
8
|
* live participant handle.
|
|
9
9
|
*/
|
|
10
|
-
export * from '@abloatai/transaction/resources/httpResources';
|
|
11
|
-
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions,
|
|
12
|
-
export type { ModelOperations } from './
|
|
10
|
+
export * from '@abloatai/transaction/client/resources/httpResources';
|
|
11
|
+
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelReadParams, ModelWriteOptions, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimContentionOptions, ClaimAttemptEvent, ClaimQueueView, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, } from '@abloatai/transaction/client/resources/modelOperations';
|
|
12
|
+
export type { ModelOperations } from './createModelOperations.js';
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* commit time because it does not exist yet when the executor is created. A
|
|
5
5
|
* client wires this up automatically unless you supply your own executor.
|
|
6
6
|
*/
|
|
7
|
-
import type { ReadDependency
|
|
7
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
8
8
|
import type { MutationExecutor, MutationOperation } from '../interfaces/index.js';
|
|
9
9
|
import type { CommitAck } from '../sync/commitFrames.js';
|
|
10
|
-
import type { CommitReceiptWire } from '@abloatai/transaction/
|
|
10
|
+
import type { CommitReceiptWire } from '@abloatai/transaction/commit';
|
|
11
11
|
import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
|
|
12
12
|
/**
|
|
13
13
|
* Creates the default mutation executor, which sends each change as a `commit`
|
|
@@ -25,6 +25,6 @@ import { type ReadSetContext } from '@abloatai/transaction/internal/read-set';
|
|
|
25
25
|
* generation below exists only for direct, one-shot executor consumers.
|
|
26
26
|
*/
|
|
27
27
|
export declare function createDefaultMutationExecutor(getWs: () => {
|
|
28
|
-
sendCommitReceipt?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null
|
|
29
|
-
sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null
|
|
28
|
+
sendCommitReceipt?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null) => Promise<CommitReceiptWire>;
|
|
29
|
+
sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, reads?: readonly ReadDependency[] | null) => Promise<CommitAck>;
|
|
30
30
|
} | null, readSetContext?: ReadSetContext): MutationExecutor;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* client wires this up automatically unless you supply your own executor.
|
|
6
6
|
*/
|
|
7
7
|
import { recordWebSocketCommitReceipt, } from '@abloatai/transaction/internal/read-set';
|
|
8
|
-
import { commitAckSchema } from '@abloatai/transaction/
|
|
8
|
+
import { commitAckSchema } from '@abloatai/transaction/commit';
|
|
9
9
|
import { AbloError, AbloConnectionError } from '@abloatai/transaction/errors';
|
|
10
10
|
// ── Default mutation executor (wire: `commit` frame over WebSocket) ──────
|
|
11
11
|
/**
|
|
@@ -36,20 +36,19 @@ export function createDefaultMutationExecutor(getWs, readSetContext) {
|
|
|
36
36
|
: `tx_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`);
|
|
37
37
|
try {
|
|
38
38
|
if (ws.sendCommitReceipt) {
|
|
39
|
-
const receipt = await ws.sendCommitReceipt(operations, clientTxId, undefined, options?.reads
|
|
39
|
+
const receipt = await ws.sendCommitReceipt(operations, clientTxId, undefined, options?.reads);
|
|
40
40
|
recordWebSocketCommitReceipt(readSetContext, {
|
|
41
41
|
receipt,
|
|
42
42
|
operations,
|
|
43
43
|
reads: options?.reads,
|
|
44
|
-
track: options?.track,
|
|
45
44
|
});
|
|
46
45
|
return commitAckSchema.parse({
|
|
47
46
|
status: receipt.status,
|
|
48
47
|
statusAt: receipt.statusAt,
|
|
49
48
|
lastSyncId: receipt.lastSyncId,
|
|
50
49
|
...(receipt.correlationId ? { correlationId: receipt.correlationId } : {}),
|
|
51
|
-
...(receipt.notifications ? { notifications: receipt.notifications } : {}),
|
|
52
50
|
...(receipt.missingIds ? { missingIds: receipt.missingIds } : {}),
|
|
51
|
+
...(receipt.operationResults ? { operationResults: receipt.operationResults } : {}),
|
|
53
52
|
});
|
|
54
53
|
}
|
|
55
54
|
if (!ws.sendCommit) {
|
|
@@ -58,7 +57,7 @@ export function createDefaultMutationExecutor(getWs, readSetContext) {
|
|
|
58
57
|
});
|
|
59
58
|
}
|
|
60
59
|
return await ws.sendCommit(operations, clientTxId, undefined, // use sendCommit's built-in 15s default; no per-call override
|
|
61
|
-
options?.reads
|
|
60
|
+
options?.reads);
|
|
62
61
|
}
|
|
63
62
|
catch (err) {
|
|
64
63
|
// Wrap transport-level failures as connection errors so the transaction
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* `ObservabilityProvider`, preserving `Ablo({ observability: new ClaimLog() })`
|
|
6
6
|
* without leaking bootstrap, storage, or WebSocket vocabulary into the core.
|
|
7
7
|
*/
|
|
8
|
-
import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/
|
|
8
|
+
import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/claims';
|
|
9
9
|
import type { ObservabilityProvider } from '../interfaces/index.js';
|
|
10
|
-
export { formatClaim, formatConflict } from '@abloatai/transaction/
|
|
11
|
-
export type { ClaimLogEntry } from '@abloatai/transaction/
|
|
10
|
+
export { formatClaim, formatConflict } from '@abloatai/transaction/claims';
|
|
11
|
+
export type { ClaimLogEntry } from '@abloatai/transaction/claims';
|
|
12
12
|
export declare class ClaimLog extends CoordinationClaimLog implements ObservabilityProvider {
|
|
13
13
|
setContext(): void;
|
|
14
14
|
setConnectionState(): void;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* `ObservabilityProvider`, preserving `Ablo({ observability: new ClaimLog() })`
|
|
6
6
|
* without leaking bootstrap, storage, or WebSocket vocabulary into the core.
|
|
7
7
|
*/
|
|
8
|
-
import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/
|
|
9
|
-
export { formatClaim, formatConflict } from '@abloatai/transaction/
|
|
8
|
+
import { ClaimLog as CoordinationClaimLog } from '@abloatai/transaction/claims';
|
|
9
|
+
export { formatClaim, formatConflict } from '@abloatai/transaction/claims';
|
|
10
10
|
export class ClaimLog extends CoordinationClaimLog {
|
|
11
11
|
// Client-lifecycle hooks the claim log has no notion of. Every body below is
|
|
12
12
|
// deliberately inert: the log records claims and conflicts, nothing else.
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
* session-error detection, online-status checks, and the transport that carries
|
|
7
7
|
* mutations to your backend. The SDK ships sensible no-op defaults where it can.
|
|
8
8
|
*/
|
|
9
|
-
import type { MutationCommitResultInput } from '@abloatai/transaction/
|
|
10
|
-
|
|
11
|
-
export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/coordination/events';
|
|
9
|
+
import type { MutationCommitResultInput } from '@abloatai/transaction/commit';
|
|
10
|
+
export type { ClaimEvent, ConflictEvent } from '@abloatai/transaction/claims/events';
|
|
12
11
|
import type { CoordinationObservability } from '@abloatai/transaction/observability';
|
|
13
12
|
export type { CoordinationObservability } from '@abloatai/transaction/observability';
|
|
14
13
|
export type { Logger } from '@abloatai/transaction/logger';
|
|
@@ -133,8 +132,8 @@ export interface ModelDebugLoggerContract {
|
|
|
133
132
|
* while every explicit queued result requires a WAL correlation.
|
|
134
133
|
*/
|
|
135
134
|
export type MutationCommitResult = MutationCommitResultInput;
|
|
136
|
-
export type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
|
|
137
|
-
import type { MutationOptions } from '@abloatai/transaction/resources/mutationOptions';
|
|
135
|
+
export type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
|
|
136
|
+
import type { MutationOptions } from '@abloatai/transaction/client/resources/mutationOptions';
|
|
138
137
|
/**
|
|
139
138
|
* The subset of {@link MutationOptions} that travels with each write as it is
|
|
140
139
|
* queued offline and sent on the wire. A single shared type keeps the public
|
|
@@ -142,7 +141,7 @@ import type { MutationOptions } from '@abloatai/transaction/resources/mutationOp
|
|
|
142
141
|
* `claim` are deliberately absent: both are resolved on the client before a write
|
|
143
142
|
* is staged, so neither reaches this layer.
|
|
144
143
|
*/
|
|
145
|
-
export type WriteOptions = Pick<MutationOptions, 'readAt' | '
|
|
144
|
+
export type WriteOptions = Pick<MutationOptions, 'readAt' | 'idempotencyKey' | 'label' | 'fenceToken' | 'claimRef'>;
|
|
146
145
|
/** A single mutation within a batch. Its `options` travel with it so the server
|
|
147
146
|
* can cache and replay the operation for idempotent retries. */
|
|
148
147
|
export interface MutationOperation {
|
|
@@ -165,7 +164,6 @@ export interface MutationOperation {
|
|
|
165
164
|
/** Server-issued claim identity the operation is attributed to. */
|
|
166
165
|
claimId?: string | null;
|
|
167
166
|
readAt?: number | null;
|
|
168
|
-
onStale?: OnStaleMode | null;
|
|
169
167
|
/**
|
|
170
168
|
* The fencing token (Option B) carried on the wire for this op — the held
|
|
171
169
|
* claim's token, validated against the entity's high-water at commit.
|
|
@@ -17,8 +17,9 @@ type UpdatePatch<S extends Schema, K extends keyof S['models'] & string> = {
|
|
|
17
17
|
} & Partial<InferModel<S, K>>;
|
|
18
18
|
export interface MutateActions<S extends Schema, K extends keyof S['models'] & string> {
|
|
19
19
|
/**
|
|
20
|
-
* Create one entity, or an array of entities in a single tick.
|
|
21
|
-
*
|
|
20
|
+
* Create one entity, or an array of entities in a single tick. The id and
|
|
21
|
+
* the tenancy value default per row; every other column, audit timestamps
|
|
22
|
+
* included, comes from the declared fields.
|
|
22
23
|
*/
|
|
23
24
|
create(data: InferCreate<S, K>): Promise<InferModel<S, K>>;
|
|
24
25
|
create(data: InferCreate<S, K>[]): Promise<InferModel<S, K>[]>;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* key — for example, fetching every block whose `sectionId` falls in a set of
|
|
14
14
|
* ids.
|
|
15
15
|
*/
|
|
16
|
-
export type { WherePrimitive, WhereOp, WhereClause, LoadWhere, } from '@abloatai/transaction/resources/where';
|
|
17
|
-
import type { WhereClause } from '@abloatai/transaction/resources/where';
|
|
16
|
+
export type { WherePrimitive, WhereOp, WhereClause, LoadWhere, } from '@abloatai/transaction/client/resources/where';
|
|
17
|
+
import type { WhereClause } from '@abloatai/transaction/client/resources/where';
|
|
18
18
|
import type { ModelListEvidence } from '@abloatai/transaction/wire';
|
|
19
19
|
/** A single structured fetch request. */
|
|
20
20
|
export interface Query {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* schema, while this one adds the local `__class` discriminator IndexedDB uses.
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import { syncDeltaActionSchema } from '@abloatai/transaction/
|
|
8
|
+
import { syncDeltaActionSchema } from '@abloatai/transaction/observation';
|
|
9
9
|
export const syncActionSchema = z.object({
|
|
10
10
|
id: z.number(),
|
|
11
11
|
modelName: z.string(),
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* unchanged; the reactive store mirrors its transitions through
|
|
6
6
|
* `ConnectionCallbacks.onStateChange` into its own observable `syncStatus`.
|
|
7
7
|
*/
|
|
8
|
-
export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '@abloatai/transaction/transport/
|
|
8
|
+
export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '@abloatai/transaction/transport/connection';
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* unchanged; the reactive store mirrors its transitions through
|
|
6
6
|
* `ConnectionCallbacks.onStateChange` into its own observable `syncStatus`.
|
|
7
7
|
*/
|
|
8
|
-
export { ConnectionManager, } from '@abloatai/transaction/transport/
|
|
8
|
+
export { ConnectionManager, } from '@abloatai/transaction/transport/connection';
|
|
@@ -34,7 +34,7 @@ import type { ModelRegistry } from '../ModelRegistry.js';
|
|
|
34
34
|
import type { RuntimeContext } from '../RuntimeContext.js';
|
|
35
35
|
import type { RecoveryClass } from '@abloatai/transaction/errorCodes';
|
|
36
36
|
import type { LoadWhere, WhereClause } from '../query/types.js';
|
|
37
|
-
import { normalizeWhere } from '@abloatai/transaction/resources/where';
|
|
37
|
+
import { normalizeWhere } from '@abloatai/transaction/client/resources/where';
|
|
38
38
|
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
39
39
|
import type { LogPositionPort } from '../logPosition.js';
|
|
40
40
|
export interface OnDemandLoaderOptions {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
import { ModelScope } from '../InstanceCache.js';
|
|
31
31
|
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
32
32
|
import { postQuery } from '../query/client.js';
|
|
33
|
-
import { normalizeWhere } from '@abloatai/transaction/resources/where';
|
|
33
|
+
import { normalizeWhere } from '@abloatai/transaction/client/resources/where';
|
|
34
34
|
const LOCAL = { kind: 'local' };
|
|
35
35
|
/**
|
|
36
36
|
* The position a returned row provably reflects: the greater of its own
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* and open/close hooks with the materialisation the transport deliberately
|
|
10
10
|
* does not do.
|
|
11
11
|
*/
|
|
12
|
-
import { type ClientSyncDelta } from '@abloatai/transaction/
|
|
13
|
-
import { WsTransport, type WsTransportOptions, type EventMap, type DefaultCollaborationEvents } from '@abloatai/transaction/transport/
|
|
12
|
+
import { type ClientSyncDelta } from '@abloatai/transaction/observation';
|
|
13
|
+
import { WsTransport, type WsTransportOptions, type EventMap, type DefaultCollaborationEvents } from '@abloatai/transaction/transport/websocket';
|
|
14
14
|
export type { CommitAck } from './commitFrames.js';
|
|
15
|
-
export type { SyncCapabilities, BootstrapHint, BootstrapDataEvent, PresenceUpdate, CoreSyncEventMap, DefaultCollaborationEvents, EventMap, SyncWebSocketEventMap, } from '@abloatai/transaction/transport/
|
|
15
|
+
export type { SyncCapabilities, BootstrapHint, BootstrapDataEvent, PresenceUpdate, CoreSyncEventMap, DefaultCollaborationEvents, EventMap, SyncWebSocketEventMap, } from '@abloatai/transaction/transport/websocket';
|
|
16
16
|
/**
|
|
17
17
|
* The wire delta the client receives. It is inferred from the canonical
|
|
18
18
|
* `clientSyncDeltaSchema` so the client and server share one contract rather
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* does not do.
|
|
11
11
|
*/
|
|
12
12
|
import { getContext } from '../context.js';
|
|
13
|
-
import { clientSyncDeltaSchema } from '@abloatai/transaction/
|
|
13
|
+
import { clientSyncDeltaSchema } from '@abloatai/transaction/observation';
|
|
14
14
|
import { drainProfilingEnabled, observeDrainStage } from './drainProfile.js';
|
|
15
|
-
import { WsTransport, } from '@abloatai/transaction/transport/
|
|
15
|
+
import { WsTransport, } from '@abloatai/transaction/transport/websocket';
|
|
16
16
|
import { isRecord } from './wsFrameHandlers.js';
|
|
17
17
|
// Sync-position state (lastSyncId watermark, version vector, server cursor).
|
|
18
18
|
import { SyncCursor } from './syncCursor.js';
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* takes the structural `CommitFrameOperation` slice, which this package's
|
|
6
6
|
* `MutationOperation` satisfies.
|
|
7
7
|
*/
|
|
8
|
-
export { buildCommitFrame,
|
|
8
|
+
export { buildCommitFrame, recordClaim, type CommitAck, type CommitFrameOperation, type ClaimTracePorts, } from '@abloatai/transaction/transport/websocket';
|
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* takes the structural `CommitFrameOperation` slice, which this package's
|
|
6
6
|
* `MutationOperation` satisfies.
|
|
7
7
|
*/
|
|
8
|
-
export { buildCommitFrame,
|
|
8
|
+
export { buildCommitFrame, recordClaim, } from '@abloatai/transaction/transport/websocket';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination';
|
|
2
|
+
import { AbloStaleContextError } from '@abloatai/transaction/errors';
|
|
3
|
+
import type { InstanceCache } from '../InstanceCache.js';
|
|
4
|
+
export interface ContextOnChangeTransport {
|
|
5
|
+
subscribe(event: string, listener: (value?: unknown) => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
/** Match context reads on the reactive client's existing delta connection. */
|
|
8
|
+
export declare function contextOnChange(transport: ContextOnChangeTransport, pool: Pick<InstanceCache, 'peek' | 'watermarks'>, reads: readonly ReadDependency[], listener: (error: AbloStaleContextError) => void): () => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AbloStaleContextError } from '@abloatai/transaction/errors';
|
|
2
|
+
/** Match context reads on the reactive client's existing delta connection. */
|
|
3
|
+
export function contextOnChange(transport, pool, reads, listener) {
|
|
4
|
+
let stopped = false;
|
|
5
|
+
const rowReads = reads.filter((read) => 'model' in read);
|
|
6
|
+
const stopDelta = transport.subscribe('delta', (value) => {
|
|
7
|
+
if (isDelta(value))
|
|
8
|
+
changedBy([value]);
|
|
9
|
+
});
|
|
10
|
+
const stopBatch = transport.subscribe('delta_batch', (value) => {
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
changedBy(value.filter(isDelta));
|
|
13
|
+
});
|
|
14
|
+
function stop() {
|
|
15
|
+
if (stopped)
|
|
16
|
+
return;
|
|
17
|
+
stopped = true;
|
|
18
|
+
stopDelta();
|
|
19
|
+
stopBatch();
|
|
20
|
+
}
|
|
21
|
+
function changedBy(deltas) {
|
|
22
|
+
if (stopped)
|
|
23
|
+
return;
|
|
24
|
+
for (const delta of deltas) {
|
|
25
|
+
const read = rowReads.find((candidate) => candidate.model.toLowerCase() === delta.modelName.toLowerCase() &&
|
|
26
|
+
candidate.id === delta.modelId &&
|
|
27
|
+
delta.id > candidate.readAt);
|
|
28
|
+
if (!read)
|
|
29
|
+
continue;
|
|
30
|
+
stop();
|
|
31
|
+
listener(staleError(read.model, read.id, read.readAt, delta.id));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Subscribe first, then inspect the resident row. A delta that lands between
|
|
36
|
+
// the original read and this attachment is either observed above or has
|
|
37
|
+
// already advanced this exact row in the pool.
|
|
38
|
+
for (const read of rowReads) {
|
|
39
|
+
const resident = pool.peek(read.id);
|
|
40
|
+
if (!resident || resident.getModelName().toLowerCase() !== read.model.toLowerCase()) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const observed = pool.watermarks.of(resident);
|
|
44
|
+
if (observed !== undefined && observed > read.readAt) {
|
|
45
|
+
stop();
|
|
46
|
+
listener(staleError(read.model, read.id, read.readAt, observed));
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return stop;
|
|
51
|
+
}
|
|
52
|
+
function staleError(model, id, readAt, observedSyncId) {
|
|
53
|
+
return new AbloStaleContextError('Context changed after read.', {
|
|
54
|
+
code: 'stale_context',
|
|
55
|
+
readAt,
|
|
56
|
+
conflicts: [{ model, id, observedSyncId }],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function isDelta(value) {
|
|
60
|
+
return (typeof value === 'object' &&
|
|
61
|
+
value !== null &&
|
|
62
|
+
typeof value.id === 'number' &&
|
|
63
|
+
typeof value.modelName === 'string' &&
|
|
64
|
+
typeof value.modelId === 'string');
|
|
65
|
+
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* • Inbound `claim_rejected` — the server refused the claim, with conflict
|
|
22
22
|
* metadata.
|
|
23
23
|
*/
|
|
24
|
-
import type { WsTransport } from '@abloatai/transaction/transport/
|
|
24
|
+
import type { WsTransport } from '@abloatai/transaction/transport/websocket';
|
|
25
25
|
import type { ClaimOptions, Claim, ClaimStream, PresenceTarget } from '@abloatai/transaction/types/streams';
|
|
26
26
|
import { type Logger } from '@abloatai/transaction/logger';
|
|
27
27
|
/**
|