@abloatai/transaction 0.48.0 → 0.50.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/CONVENTIONS.md +7 -7
- package/README.md +1 -1
- package/dist/ai-sdk/modelTools.d.ts +1 -1
- package/dist/ai-sdk/modelTools.js +1 -1
- package/dist/ai-sdk/updateTool.d.ts +2 -2
- package/dist/ai-sdk/updateTool.d.ts.map +1 -1
- package/dist/auth/bootstrapScope.d.ts +1 -1
- package/dist/auth/capability.d.ts +32 -10
- package/dist/auth/capability.d.ts.map +1 -1
- package/dist/auth/capability.js +9 -3
- package/dist/auth/capability.js.map +1 -1
- package/dist/auth/capabilityLifecycle.d.ts +2 -2
- package/dist/auth/capabilityLifecycle.d.ts.map +1 -1
- package/dist/auth/capabilityLifecycle.js +3 -2
- package/dist/auth/capabilityLifecycle.js.map +1 -1
- package/dist/auth/identity.d.ts +3 -0
- package/dist/auth/identity.d.ts.map +1 -1
- package/dist/auth/identity.js +13 -1
- package/dist/auth/identity.js.map +1 -1
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/schemas.d.ts +18 -1
- package/dist/auth/schemas.d.ts.map +1 -1
- package/dist/auth/schemas.js +3 -1
- package/dist/auth/schemas.js.map +1 -1
- package/dist/auth/sessionMint.d.ts.map +1 -1
- package/dist/auth/sessionMint.js +0 -1
- package/dist/auth/sessionMint.js.map +1 -1
- package/dist/commitRecordRuntime.d.ts +30 -0
- package/dist/commitRecordRuntime.d.ts.map +1 -0
- package/dist/commitRecordRuntime.js +188 -0
- package/dist/commitRecordRuntime.js.map +1 -0
- package/dist/coordination/index.d.ts +3 -3
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2 -2
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/locator.d.ts +5 -0
- package/dist/coordination/locator.d.ts.map +1 -1
- package/dist/coordination/locator.js +12 -0
- package/dist/coordination/locator.js.map +1 -1
- package/dist/coordination/schema.d.ts +205 -15
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +88 -47
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/targetConflict.js +1 -1
- package/dist/durableWrites.d.ts +3 -3
- package/dist/durableWrites.d.ts.map +1 -1
- package/dist/durableWrites.js +1 -1
- package/dist/errorCodes.d.ts +20 -1
- package/dist/errorCodes.d.ts.map +1 -1
- package/dist/errorCodes.js +44 -10
- package/dist/errorCodes.js.map +1 -1
- package/dist/errorObservation.d.ts +82 -0
- package/dist/errorObservation.d.ts.map +1 -0
- package/dist/errorObservation.js +101 -0
- package/dist/errorObservation.js.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/read-set.d.ts +8 -0
- package/dist/internal/read-set.d.ts.map +1 -0
- package/dist/internal/read-set.js +7 -0
- package/dist/internal/read-set.js.map +1 -0
- package/dist/logger.d.ts +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/readSetContext.d.ts +65 -0
- package/dist/readSetContext.d.ts.map +1 -0
- package/dist/readSetContext.js +121 -0
- package/dist/readSetContext.js.map +1 -0
- package/dist/resources/functionalUpdate.d.ts +13 -3
- package/dist/resources/functionalUpdate.d.ts.map +1 -1
- package/dist/resources/functionalUpdate.js.map +1 -1
- package/dist/resources/httpResources.d.ts +23 -7
- package/dist/resources/httpResources.d.ts.map +1 -1
- package/dist/resources/modelOperations.d.ts +2 -2
- package/dist/resources/modelOperations.js +1 -1
- package/dist/resources/mutationOptions.d.ts +1 -1
- package/dist/resources/mutationOptions.js +1 -1
- package/dist/resources/where.d.ts +1 -1
- package/dist/resources/where.js +1 -1
- package/dist/resources/writeOptionsSchema.d.ts +39 -4
- package/dist/resources/writeOptionsSchema.d.ts.map +1 -1
- package/dist/resources/writeOptionsSchema.js +12 -6
- package/dist/resources/writeOptionsSchema.js.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/openapi.d.ts.map +1 -1
- package/dist/schema/openapi.js +41 -7
- package/dist/schema/openapi.js.map +1 -1
- package/dist/server/commit.d.ts +7 -1
- package/dist/server/commit.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/source/adapters/kysely.d.ts.map +1 -1
- package/dist/source/adapters/kysely.js +11 -4
- package/dist/source/adapters/kysely.js.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.d.ts.map +1 -1
- package/dist/source/adapters/kyselyMutationCore.js +22 -4
- package/dist/source/adapters/kyselyMutationCore.js.map +1 -1
- package/dist/source/contract.d.ts +2 -0
- package/dist/source/contract.d.ts.map +1 -1
- package/dist/source/types.d.ts +1 -1
- package/dist/testing/fixtures/httpResponses.d.ts +30 -1
- package/dist/testing/fixtures/httpResponses.d.ts.map +1 -1
- package/dist/testing/fixtures/httpResponses.js +48 -1
- package/dist/testing/fixtures/httpResponses.js.map +1 -1
- package/dist/transactionLayer.d.ts +1 -1
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.d.ts +4 -2
- package/dist/transactions/confirmation/commitEnvelope.d.ts.map +1 -0
- package/dist/transactions/{settlement → confirmation}/commitEnvelope.js +8 -3
- package/dist/transactions/confirmation/commitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.d.ts.map +1 -0
- package/dist/transactions/confirmation/httpCommitEnvelope.js.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.d.ts.map +1 -0
- package/dist/transactions/confirmation/idempotencyKey.js.map +1 -0
- package/dist/transactions/{settlement → confirmation}/pendingWrite.d.ts +3 -2
- package/dist/transactions/confirmation/pendingWrite.d.ts.map +1 -0
- package/dist/transactions/confirmation/pendingWrite.js.map +1 -0
- package/dist/transport/commitFrames.d.ts +1 -0
- package/dist/transport/commitFrames.d.ts.map +1 -1
- package/dist/transport/commitFrames.js +8 -7
- package/dist/transport/commitFrames.js.map +1 -1
- package/dist/transport/connectionManager.d.ts +1 -1
- package/dist/transport/connectionManager.js +1 -1
- package/dist/transport/httpClient.d.ts +23 -9
- package/dist/transport/httpClient.d.ts.map +1 -1
- package/dist/transport/httpClient.js +137 -15
- package/dist/transport/httpClient.js.map +1 -1
- package/dist/transport/httpOptions.d.ts +9 -1
- package/dist/transport/httpOptions.d.ts.map +1 -1
- package/dist/transport/httpTransport.d.ts +3 -0
- package/dist/transport/httpTransport.d.ts.map +1 -1
- package/dist/transport/httpTransport.js +109 -22
- package/dist/transport/httpTransport.js.map +1 -1
- package/dist/transport/wsFrameHandlers.d.ts +3 -1
- package/dist/transport/wsFrameHandlers.d.ts.map +1 -1
- package/dist/transport/wsFrameHandlers.js +38 -17
- package/dist/transport/wsFrameHandlers.js.map +1 -1
- package/dist/transport/wsTransport.d.ts +3 -0
- package/dist/transport/wsTransport.d.ts.map +1 -1
- package/dist/transport/wsTransport.js +32 -1
- package/dist/transport/wsTransport.js.map +1 -1
- package/dist/utils/json.d.ts +1 -1
- package/dist/utils/json.js +1 -1
- package/dist/wire/accountResponses.d.ts +1 -1
- package/dist/wire/auth.d.ts +2 -2
- package/dist/wire/commit.d.ts +1652 -213
- package/dist/wire/commit.d.ts.map +1 -1
- package/dist/wire/commit.js +196 -132
- package/dist/wire/commit.js.map +1 -1
- package/dist/wire/delta.d.ts +1 -1
- package/dist/wire/errorEnvelope.d.ts +1 -0
- package/dist/wire/errorEnvelope.d.ts.map +1 -1
- package/dist/wire/errorEnvelope.js +1 -0
- package/dist/wire/errorEnvelope.js.map +1 -1
- package/dist/wire/feedCursor.d.ts +1 -1
- package/dist/wire/feedCursor.js +1 -1
- package/dist/wire/feedEvent.d.ts +3 -3
- package/dist/wire/frames.d.ts +8 -4
- package/dist/wire/frames.d.ts.map +1 -1
- package/dist/wire/frames.js +6 -3
- package/dist/wire/frames.js.map +1 -1
- package/dist/wire/inboundFrames.d.ts +44 -4
- package/dist/wire/inboundFrames.d.ts.map +1 -1
- package/dist/wire/index.d.ts +6 -4
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +6 -5
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/modelMutations.d.ts +35 -0
- package/dist/wire/modelMutations.d.ts.map +1 -1
- package/dist/wire/modelMutations.js +9 -2
- package/dist/wire/modelMutations.js.map +1 -1
- package/dist/wire/modelResponses.d.ts +12 -1
- package/dist/wire/modelResponses.d.ts.map +1 -1
- package/dist/wire/modelResponses.js +12 -1
- package/dist/wire/modelResponses.js.map +1 -1
- package/package.json +12 -2
- package/src/ai-sdk/modelTools.ts +1 -1
- package/src/ai-sdk/updateTool.ts +2 -2
- package/src/auth/bootstrapScope.ts +1 -1
- package/src/auth/capability.ts +13 -4
- package/src/auth/capabilityLifecycle.ts +3 -2
- package/src/auth/identity.ts +16 -1
- package/src/auth/index.ts +2 -0
- package/src/auth/schemas.ts +3 -0
- package/src/auth/sessionMint.ts +0 -1
- package/src/commitRecordRuntime.ts +245 -0
- package/src/coordination/index.ts +24 -1
- package/src/coordination/locator.ts +17 -0
- package/src/coordination/schema.ts +114 -49
- package/src/coordination/targetConflict.ts +1 -1
- package/src/durableWrites.ts +3 -3
- package/src/errorCodes.ts +107 -11
- package/src/errorObservation.ts +121 -0
- package/src/errors.ts +5 -1
- package/src/index.ts +8 -1
- package/src/internal/read-set.ts +23 -0
- package/src/logger.ts +1 -1
- package/src/observability.ts +1 -1
- package/src/readSetContext.ts +195 -0
- package/src/resources/functionalUpdate.ts +16 -4
- package/src/resources/httpResources.ts +27 -8
- package/src/resources/modelOperations.ts +2 -2
- package/src/resources/mutationOptions.ts +1 -1
- package/src/resources/where.ts +1 -1
- package/src/resources/writeOptionsSchema.ts +30 -12
- package/src/schema/index.ts +1 -1
- package/src/schema/openapi.ts +48 -7
- package/src/server/commit.ts +7 -1
- package/src/server/index.ts +5 -1
- package/src/source/adapters/kysely.ts +12 -4
- package/src/source/adapters/kyselyMutationCore.ts +28 -4
- package/src/source/types.ts +1 -1
- package/src/testing/fixtures/httpResponses.ts +60 -1
- package/src/transactionLayer.ts +1 -1
- package/src/transactions/{settlement → confirmation}/commitEnvelope.ts +13 -3
- package/src/transport/commitFrames.ts +7 -5
- package/src/transport/connectionManager.ts +1 -1
- package/src/transport/httpClient.ts +260 -29
- package/src/transport/httpOptions.ts +9 -1
- package/src/transport/httpTransport.ts +139 -27
- package/src/transport/wsFrameHandlers.ts +46 -8
- package/src/transport/wsTransport.ts +42 -1
- package/src/utils/json.ts +1 -1
- package/src/wire/commit.ts +248 -147
- package/src/wire/errorEnvelope.ts +1 -0
- package/src/wire/feedCursor.ts +1 -1
- package/src/wire/frames.ts +9 -4
- package/src/wire/index.ts +38 -5
- package/src/wire/modelMutations.ts +16 -2
- package/src/wire/modelResponses.ts +14 -1
- package/dist/transactions/settlement/commitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/commitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.d.ts.map +0 -1
- package/dist/transactions/settlement/httpCommitEnvelope.js.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.d.ts.map +0 -1
- package/dist/transactions/settlement/idempotencyKey.js.map +0 -1
- package/dist/transactions/settlement/pendingWrite.d.ts.map +0 -1
- package/dist/transactions/settlement/pendingWrite.js.map +0 -1
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/httpCommitEnvelope.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.d.ts +0 -0
- /package/dist/transactions/{settlement → confirmation}/idempotencyKey.js +0 -0
- /package/dist/transactions/{settlement → confirmation}/pendingWrite.js +0 -0
- /package/src/transactions/{settlement → confirmation}/httpCommitEnvelope.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/idempotencyKey.ts +0 -0
- /package/src/transactions/{settlement → confirmation}/pendingWrite.ts +0 -0
|
@@ -22,7 +22,7 @@ import { z } from 'zod';
|
|
|
22
22
|
import {
|
|
23
23
|
reconcileFunctionalUpdate,
|
|
24
24
|
type ModelUpdater,
|
|
25
|
-
type
|
|
25
|
+
type FunctionalUpdateOptions,
|
|
26
26
|
} from '../resources/functionalUpdate.js';
|
|
27
27
|
import {
|
|
28
28
|
assertBrowserSafety,
|
|
@@ -36,7 +36,14 @@ import {
|
|
|
36
36
|
warnIfCliKeyMismatch,
|
|
37
37
|
} from '../auth/apiKey.js';
|
|
38
38
|
import { PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER } from '../wire/protocolVersion.js';
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
commitReceiptSchema,
|
|
41
|
+
commitRecordSchema,
|
|
42
|
+
commitRecordListSchema,
|
|
43
|
+
commitRecordListOptionsSchema,
|
|
44
|
+
commitRecordWhereSchema,
|
|
45
|
+
type CommitReceiptWire,
|
|
46
|
+
} from '../wire/commit.js';
|
|
40
47
|
import { logListResponseSchema } from '../wire/feedEvent.js';
|
|
41
48
|
import {
|
|
42
49
|
claimAcquireResponseSchema,
|
|
@@ -89,6 +96,7 @@ import {
|
|
|
89
96
|
rotateCapability,
|
|
90
97
|
} from '../auth/capabilityLifecycle.js';
|
|
91
98
|
import { parseIdentityResolveResponse } from '../auth/schemas.js';
|
|
99
|
+
import type { EffectiveAuthority } from '../auth/capability.js';
|
|
92
100
|
|
|
93
101
|
/**
|
|
94
102
|
* Interpret a heartbeat reply for a lease this handle HOLDS: anything other
|
|
@@ -136,6 +144,7 @@ import {
|
|
|
136
144
|
subTarget,
|
|
137
145
|
streamTarget,
|
|
138
146
|
batchFence,
|
|
147
|
+
claimIdFor,
|
|
139
148
|
fenceTokenFor,
|
|
140
149
|
} from '../coordination/locator.js';
|
|
141
150
|
import { declaredMeta, wireMeta } from '../coordination/claimMeta.js';
|
|
@@ -150,8 +159,8 @@ import {
|
|
|
150
159
|
isHttpCommitReplayExpired,
|
|
151
160
|
type DurableHttpCommitEnvelope,
|
|
152
161
|
type DurableHttpCommitMethod,
|
|
153
|
-
} from '../transactions/
|
|
154
|
-
import type { CommitOutboxScope } from '../transactions/
|
|
162
|
+
} from '../transactions/confirmation/httpCommitEnvelope.js';
|
|
163
|
+
import type { CommitOutboxScope } from '../transactions/confirmation/commitEnvelope.js';
|
|
155
164
|
import { resolveDurableWrites } from '../durableWrites.js';
|
|
156
165
|
|
|
157
166
|
/** @internal Private options for the schema-agnostic HTTP protocol transport. */
|
|
@@ -204,6 +213,8 @@ export interface HttpTransport {
|
|
|
204
213
|
*/
|
|
205
214
|
readonly claims: HttpClaimsResource;
|
|
206
215
|
readonly logs: HttpLogsResource;
|
|
216
|
+
/** Server-confirmed authority of the active bearer, populated by `ready()`. */
|
|
217
|
+
readonly identity: EffectiveAuthority | null;
|
|
207
218
|
model<T = Record<string, unknown>, Fields = T>(
|
|
208
219
|
name: string,
|
|
209
220
|
): HttpTransportModel<T, Fields>;
|
|
@@ -348,6 +359,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
348
359
|
let httpOutboxScopeNamespace: string | null = null;
|
|
349
360
|
|
|
350
361
|
let readyPromise: Promise<void> | null = null;
|
|
362
|
+
let effectiveAuthority: EffectiveAuthority | null = null;
|
|
351
363
|
let httpCommitLane: Promise<void> = Promise.resolve();
|
|
352
364
|
|
|
353
365
|
function runInHttpCommitLane<T>(work: () => Promise<T>): Promise<T> {
|
|
@@ -385,7 +397,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
385
397
|
return httpOutboxScopeNamespace;
|
|
386
398
|
}
|
|
387
399
|
|
|
388
|
-
async function
|
|
400
|
+
async function prepare(): Promise<void> {
|
|
389
401
|
if (readyPromise) return readyPromise;
|
|
390
402
|
|
|
391
403
|
readyPromise = (async () => {
|
|
@@ -401,6 +413,14 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
401
413
|
}
|
|
402
414
|
}
|
|
403
415
|
|
|
416
|
+
async function ready(): Promise<void> {
|
|
417
|
+
await prepare();
|
|
418
|
+
if (!effectiveAuthority) {
|
|
419
|
+
const rawIdentity = await requestRaw('/auth/identity', { method: 'GET' }, true);
|
|
420
|
+
effectiveAuthority = parseIdentityResolveResponse(rawIdentity).authority;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
404
424
|
async function authHeaders(sealedProtocolVersion?: number): Promise<Record<string, string>> {
|
|
405
425
|
const apiKey = await resolveApiKeyValue(configuredApiKey);
|
|
406
426
|
const token = apiKey ?? configuredAuthToken;
|
|
@@ -463,7 +483,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
463
483
|
},
|
|
464
484
|
skipReady = false
|
|
465
485
|
): Promise<unknown> {
|
|
466
|
-
if (!skipReady) await
|
|
486
|
+
if (!skipReady) await prepare();
|
|
467
487
|
const { idempotencyKey, sealedProtocolVersion, ...requestInit } = init;
|
|
468
488
|
const headers = await authHeaders(sealedProtocolVersion);
|
|
469
489
|
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
|
|
@@ -589,7 +609,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
589
609
|
try {
|
|
590
610
|
await commitOutbox.remove(recordId);
|
|
591
611
|
} catch (cause) {
|
|
592
|
-
// Do not report the remote outcome until local
|
|
612
|
+
// Do not report the remote outcome until local confirmation is durable.
|
|
593
613
|
// The retained record can still be replayed inside the safe window.
|
|
594
614
|
throw new AbloConnectionError(
|
|
595
615
|
'The server settled the commit, but its local outbox record could not be cleared.',
|
|
@@ -652,6 +672,33 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
652
672
|
readonly sealedProtocolVersion?: number;
|
|
653
673
|
}
|
|
654
674
|
|
|
675
|
+
function observeCommitReceipt(
|
|
676
|
+
request: ExactHttpCommitRequest,
|
|
677
|
+
receipt: CommitResponse,
|
|
678
|
+
): void {
|
|
679
|
+
// A successful receipt carries the same server-authored authority as the
|
|
680
|
+
// identity exchange. Reuse it so startup replay does not add a redundant
|
|
681
|
+
// auth request before callers can inspect `identity`.
|
|
682
|
+
effectiveAuthority = receipt.authority;
|
|
683
|
+
if (!options.onCommitReceipt) return;
|
|
684
|
+
let body: unknown = request.body;
|
|
685
|
+
try {
|
|
686
|
+
body = JSON.parse(request.body) as unknown;
|
|
687
|
+
} catch {
|
|
688
|
+
// Preserve the exact opaque body for observability if it is not JSON.
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
options.onCommitReceipt({
|
|
692
|
+
receipt,
|
|
693
|
+
method: request.method,
|
|
694
|
+
path: request.path,
|
|
695
|
+
body,
|
|
696
|
+
});
|
|
697
|
+
} catch {
|
|
698
|
+
// Receipt observation must never change the commit outcome.
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
655
702
|
function replicationLagTimeout(
|
|
656
703
|
request: ExactHttpCommitRequest,
|
|
657
704
|
response: CommitResponse
|
|
@@ -713,6 +760,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
713
760
|
true
|
|
714
761
|
);
|
|
715
762
|
const next = parseSuccessfulCommitResponse(raw, request.idempotencyKey);
|
|
763
|
+
observeCommitReceipt(request, next);
|
|
716
764
|
if (next.correlationId !== correlationId) {
|
|
717
765
|
throw new AbloIdempotencyError(
|
|
718
766
|
'The same HTTP commit replay returned different source correlation evidence.',
|
|
@@ -794,6 +842,16 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
794
842
|
true
|
|
795
843
|
);
|
|
796
844
|
const response = parseSuccessfulCommitResponse(raw, envelope.idempotencyKey);
|
|
845
|
+
observeCommitReceipt(
|
|
846
|
+
{
|
|
847
|
+
idempotencyKey: envelope.idempotencyKey,
|
|
848
|
+
method: envelope.request.method,
|
|
849
|
+
path: envelope.request.path,
|
|
850
|
+
body: envelope.request.body,
|
|
851
|
+
sealedProtocolVersion: envelope.protocolVersion,
|
|
852
|
+
},
|
|
853
|
+
response,
|
|
854
|
+
);
|
|
797
855
|
if (
|
|
798
856
|
envelope.correlationId !== undefined &&
|
|
799
857
|
response.correlationId !== envelope.correlationId
|
|
@@ -929,11 +987,11 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
929
987
|
body: unknown;
|
|
930
988
|
wait: CommitWait;
|
|
931
989
|
},
|
|
932
|
-
|
|
990
|
+
beforeConfirmation?: (response: CommitResponse) => Promise<void>
|
|
933
991
|
): Promise<CommitResponse> {
|
|
934
992
|
return runInHttpCommitLane(async () => {
|
|
935
|
-
await
|
|
936
|
-
//
|
|
993
|
+
await prepare();
|
|
994
|
+
// Startup preparation covers replay. Re-draining here makes every later write wait
|
|
937
995
|
// behind an ambiguous predecessor from this same process.
|
|
938
996
|
const replayed = await replayHttpCommitOutbox();
|
|
939
997
|
const prior = replayed.get(input.idempotencyKey);
|
|
@@ -970,7 +1028,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
970
1028
|
}
|
|
971
1029
|
}
|
|
972
1030
|
if (priorResponse.status === 'confirmed') {
|
|
973
|
-
await
|
|
1031
|
+
await beforeConfirmation?.(priorResponse);
|
|
974
1032
|
await settleHttpEnvelope(prior.envelope.id);
|
|
975
1033
|
}
|
|
976
1034
|
return priorResponse;
|
|
@@ -1000,6 +1058,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1000
1058
|
true
|
|
1001
1059
|
);
|
|
1002
1060
|
response = parseSuccessfulCommitResponse(raw, input.idempotencyKey);
|
|
1061
|
+
observeCommitReceipt(exactRequest, response);
|
|
1003
1062
|
if (durableEnvelope && response.status === 'queued') {
|
|
1004
1063
|
await persistHttpAcceptance(durableEnvelope, response);
|
|
1005
1064
|
}
|
|
@@ -1013,12 +1072,12 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1013
1072
|
throw error;
|
|
1014
1073
|
}
|
|
1015
1074
|
|
|
1016
|
-
// A model-create readback can participate in
|
|
1075
|
+
// A model-create readback can participate in confirmation: if it fails,
|
|
1017
1076
|
// retain the exact write so a same-key retry recovers the generated id.
|
|
1018
1077
|
// A queued source receipt cannot be read back from the log yet and stays
|
|
1019
1078
|
// durable until a later confirmed replay.
|
|
1020
1079
|
if (response.status === 'confirmed') {
|
|
1021
|
-
await
|
|
1080
|
+
await beforeConfirmation?.(response);
|
|
1022
1081
|
if (durableEnvelope) await settleHttpEnvelope(durableEnvelope.id);
|
|
1023
1082
|
}
|
|
1024
1083
|
return response;
|
|
@@ -1044,7 +1103,8 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1044
1103
|
function normalizeCommitOperation(
|
|
1045
1104
|
op: CommitOperationInput,
|
|
1046
1105
|
defaults: Pick<CommitCreateOptions, 'readAt' | 'onStale'>,
|
|
1047
|
-
fence: BatchFence | null
|
|
1106
|
+
fence: BatchFence | null,
|
|
1107
|
+
claim: Claim | null,
|
|
1048
1108
|
): CommitOperationInput {
|
|
1049
1109
|
return {
|
|
1050
1110
|
action: op.action,
|
|
@@ -1052,6 +1112,8 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1052
1112
|
id: op.id ?? null,
|
|
1053
1113
|
data: op.data ?? null,
|
|
1054
1114
|
transactionId: op.transactionId ?? null,
|
|
1115
|
+
claimId:
|
|
1116
|
+
op.claimId ?? claimIdFor(claim?.target, claim?.id, op.model, op.id ?? null),
|
|
1055
1117
|
readAt: op.readAt ?? defaults.readAt ?? null,
|
|
1056
1118
|
onStale: op.onStale ?? defaults.onStale ?? null,
|
|
1057
1119
|
fenceToken: op.fenceToken ?? fenceTokenFor(fence, op.model, op.id ?? null),
|
|
@@ -1068,7 +1130,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1068
1130
|
});
|
|
1069
1131
|
}
|
|
1070
1132
|
return commitOptions.operations.map((op) =>
|
|
1071
|
-
normalizeCommitOperation(op, commitOptions, fence)
|
|
1133
|
+
normalizeCommitOperation(op, commitOptions, fence, commitOptions.claim ?? null)
|
|
1072
1134
|
);
|
|
1073
1135
|
}
|
|
1074
1136
|
|
|
@@ -1270,6 +1332,8 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1270
1332
|
onStale: commitOptions.onStale,
|
|
1271
1333
|
wait: commitOptions.wait,
|
|
1272
1334
|
claim: commitOptions.claim,
|
|
1335
|
+
reads: commitOptions.reads,
|
|
1336
|
+
track: commitOptions.track,
|
|
1273
1337
|
},
|
|
1274
1338
|
'commits.create'
|
|
1275
1339
|
);
|
|
@@ -1318,7 +1382,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1318
1382
|
// `requestJson` throws via `translateHttpError` on any non-2xx, so
|
|
1319
1383
|
// reaching here implies success and `body` is already the success-only
|
|
1320
1384
|
// receipt union — a rejection is a separate type that never arrives here.
|
|
1321
|
-
// The
|
|
1385
|
+
// The confirmation status therefore passes through verbatim: no branch may
|
|
1322
1386
|
// collapse a state the server reported into a different one.
|
|
1323
1387
|
return {
|
|
1324
1388
|
id: body.id ?? body.clientTxId,
|
|
@@ -1330,6 +1394,34 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1330
1394
|
...(body.missingIds && body.missingIds.length > 0 ? { missingIds: body.missingIds } : {}),
|
|
1331
1395
|
};
|
|
1332
1396
|
},
|
|
1397
|
+
get({ id }) {
|
|
1398
|
+
if (!id) {
|
|
1399
|
+
throw new AbloValidationError('commits.get requires a non-empty id.', {
|
|
1400
|
+
code: 'write_options_invalid',
|
|
1401
|
+
param: 'id',
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
return requestJson(
|
|
1405
|
+
`/v1/commits/${encodeURIComponent(id)}`,
|
|
1406
|
+
{ method: 'GET' },
|
|
1407
|
+
commitRecordSchema.nullable(),
|
|
1408
|
+
);
|
|
1409
|
+
},
|
|
1410
|
+
list(options = {}) {
|
|
1411
|
+
const parsed = commitRecordListOptionsSchema.parse(options);
|
|
1412
|
+
const where = commitRecordWhereSchema.parse(parsed.where ?? {});
|
|
1413
|
+
const params = new URLSearchParams();
|
|
1414
|
+
if (where.actorId) params.set('actorId', where.actorId);
|
|
1415
|
+
if (where.status) params.set('status', where.status);
|
|
1416
|
+
if (parsed.cursor) params.set('cursor', parsed.cursor);
|
|
1417
|
+
if (parsed.limit !== undefined) params.set('limit', String(parsed.limit));
|
|
1418
|
+
const query = params.size > 0 ? `?${params.toString()}` : '';
|
|
1419
|
+
return requestJson(
|
|
1420
|
+
`/v1/commits${query}`,
|
|
1421
|
+
{ method: 'GET' },
|
|
1422
|
+
commitRecordListSchema,
|
|
1423
|
+
);
|
|
1424
|
+
},
|
|
1333
1425
|
};
|
|
1334
1426
|
|
|
1335
1427
|
const logs: HttpLogsResource = {
|
|
@@ -1352,7 +1444,10 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1352
1444
|
},
|
|
1353
1445
|
};
|
|
1354
1446
|
|
|
1355
|
-
async function listModel<T>(
|
|
1447
|
+
async function listModel<T>(
|
|
1448
|
+
modelName: string,
|
|
1449
|
+
options?: ServerReadOptions<T>,
|
|
1450
|
+
): Promise<{ readonly data: readonly T[]; readonly evidence?: readonly { id: string; stamp: number }[] }> {
|
|
1356
1451
|
const params = new URLSearchParams();
|
|
1357
1452
|
if (options?.limit !== undefined) params.set('limit', String(options.limit));
|
|
1358
1453
|
if (options?.orderBy) {
|
|
@@ -1380,7 +1475,10 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1380
1475
|
// transport is schema-agnostic — it moves rows for whatever schema the
|
|
1381
1476
|
// caller declared, and `T` is that declaration. Row validation belongs to
|
|
1382
1477
|
// the typed facade above, which holds the model's schema.
|
|
1383
|
-
return
|
|
1478
|
+
return {
|
|
1479
|
+
data: res.data as T[],
|
|
1480
|
+
...(res.evidence ? { evidence: res.evidence } : {}),
|
|
1481
|
+
};
|
|
1384
1482
|
}
|
|
1385
1483
|
|
|
1386
1484
|
async function retrieveModel<T>(
|
|
@@ -1426,7 +1524,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1426
1524
|
id: string,
|
|
1427
1525
|
data: Record<string, unknown> | undefined,
|
|
1428
1526
|
options: ModelMutationOptions | undefined,
|
|
1429
|
-
|
|
1527
|
+
beforeConfirmation?: (response: CommitResponse) => Promise<void>
|
|
1430
1528
|
): Promise<CommitReceipt> {
|
|
1431
1529
|
assertWriteOptions(
|
|
1432
1530
|
options && {
|
|
@@ -1434,6 +1532,10 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1434
1532
|
readAt: options.readAt,
|
|
1435
1533
|
onStale: options.onStale,
|
|
1436
1534
|
claim: options.claim,
|
|
1535
|
+
claimRef: options.claimRef,
|
|
1536
|
+
fenceToken: options.fenceToken,
|
|
1537
|
+
reads: options.reads,
|
|
1538
|
+
track: options.track,
|
|
1437
1539
|
},
|
|
1438
1540
|
`${modelName} ${action}`
|
|
1439
1541
|
);
|
|
@@ -1460,6 +1562,8 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1460
1562
|
claim: normalizeClaimId(options?.claimRef) ?? claimHandle?.id,
|
|
1461
1563
|
onStale: options?.onStale ?? (claimHandle?.readAt !== undefined ? 'reject' : undefined),
|
|
1462
1564
|
readAt,
|
|
1565
|
+
reads: options?.reads,
|
|
1566
|
+
track: options?.track,
|
|
1463
1567
|
// The claim's fencing token (Option B), so the per-model HTTP write door
|
|
1464
1568
|
// fences the same as the WS proxy and `commits.create`.
|
|
1465
1569
|
fenceToken: options?.fenceToken ?? claimHandle?.fenceToken,
|
|
@@ -1480,7 +1584,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1480
1584
|
// lower-level `commits.create` lifecycle surface.
|
|
1481
1585
|
wait: 'confirmed',
|
|
1482
1586
|
},
|
|
1483
|
-
|
|
1587
|
+
beforeConfirmation
|
|
1484
1588
|
);
|
|
1485
1589
|
} catch (error) {
|
|
1486
1590
|
// The per-model write door (`ablo.<model>.update/create/delete`). Capture
|
|
@@ -1490,7 +1594,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1490
1594
|
}
|
|
1491
1595
|
|
|
1492
1596
|
// Same contract as `commits.create` above: a non-2xx already threw, so
|
|
1493
|
-
// `body` is the success-only receipt union and its
|
|
1597
|
+
// `body` is the success-only receipt union and its confirmation status passes
|
|
1494
1598
|
// through verbatim rather than through a catch-all branch.
|
|
1495
1599
|
return {
|
|
1496
1600
|
id: body.serverTxId,
|
|
@@ -1773,7 +1877,14 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1773
1877
|
if (!claimInput) return run(input);
|
|
1774
1878
|
|
|
1775
1879
|
if (isClaimHandle(claimInput)) {
|
|
1776
|
-
return run({
|
|
1880
|
+
return run({
|
|
1881
|
+
...input,
|
|
1882
|
+
claimRef: { id: claimInput.id },
|
|
1883
|
+
...(claimInput.fenceToken !== undefined
|
|
1884
|
+
? { fenceToken: claimInput.fenceToken }
|
|
1885
|
+
: {}),
|
|
1886
|
+
claim: undefined,
|
|
1887
|
+
});
|
|
1777
1888
|
}
|
|
1778
1889
|
|
|
1779
1890
|
// `isClaimHandle` ruled out the handle form above; the generic mismatch
|
|
@@ -1806,14 +1917,14 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1806
1917
|
function updateModel(
|
|
1807
1918
|
id: string,
|
|
1808
1919
|
updater: ModelUpdater<T>,
|
|
1809
|
-
options?:
|
|
1920
|
+
options?: FunctionalUpdateOptions
|
|
1810
1921
|
): Promise<CommitReceipt | undefined>;
|
|
1811
1922
|
function updateModel(
|
|
1812
1923
|
arg:
|
|
1813
1924
|
| (ModelMutationOptions & { readonly id: string; readonly data: Record<string, unknown> })
|
|
1814
1925
|
| string,
|
|
1815
1926
|
updater?: ModelUpdater<T>,
|
|
1816
|
-
contention?:
|
|
1927
|
+
contention?: FunctionalUpdateOptions
|
|
1817
1928
|
): Promise<CommitReceipt | undefined> {
|
|
1818
1929
|
// Functional form: update(id, current => next). The SDK owns the
|
|
1819
1930
|
// read-fresh → compute → compare-and-swap → reconcile loop; correctness
|
|
@@ -1857,7 +1968,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1857
1968
|
retrieve(params: ModelReadOptions & { readonly id: string }): Promise<HttpTransportRead<T>> {
|
|
1858
1969
|
return retrieveModel<T>(name, params);
|
|
1859
1970
|
},
|
|
1860
|
-
list(options?: ServerReadOptions<T>):
|
|
1971
|
+
list(options?: ServerReadOptions<T>): ReturnType<typeof listModel<T>> {
|
|
1861
1972
|
return listModel<T>(name, options);
|
|
1862
1973
|
},
|
|
1863
1974
|
async create(
|
|
@@ -1893,7 +2004,7 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1893
2004
|
}
|
|
1894
2005
|
);
|
|
1895
2006
|
if (created === undefined) {
|
|
1896
|
-
throw new AbloConnectionError('Create
|
|
2007
|
+
throw new AbloConnectionError('Create confirmation did not return its row.', {
|
|
1897
2008
|
code: 'commit_no_result',
|
|
1898
2009
|
});
|
|
1899
2010
|
}
|
|
@@ -1935,9 +2046,10 @@ export function createHttpTransport(options: HttpTransportOptions): HttpTranspor
|
|
|
1935
2046
|
|
|
1936
2047
|
return {
|
|
1937
2048
|
ready,
|
|
2049
|
+
get identity() { return effectiveAuthority; },
|
|
1938
2050
|
waitForFlush: () =>
|
|
1939
2051
|
runInHttpCommitLane(async () => {
|
|
1940
|
-
await
|
|
2052
|
+
await prepare();
|
|
1941
2053
|
const replayed = await replayHttpCommitOutbox();
|
|
1942
2054
|
await confirmReplayedHttpCommits(replayed);
|
|
1943
2055
|
}),
|
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
errorFromWire,
|
|
15
15
|
type RequiredCapability,
|
|
16
16
|
} from '../errors.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
commitAckSchema,
|
|
19
|
+
commitReceiptSchema,
|
|
20
|
+
type CommitReceiptWire,
|
|
21
|
+
} from '../wire/commit.js';
|
|
18
22
|
import { subscriptionAckPayloadSchema } from '../coordination/schema.js';
|
|
19
23
|
import {
|
|
20
24
|
WS_INBOUND_FRAMES,
|
|
@@ -35,9 +39,10 @@ import type { SocketObservability } from '../observability.js';
|
|
|
35
39
|
* server, or rejected on timeout / disconnect.
|
|
36
40
|
*/
|
|
37
41
|
export interface PendingCommit {
|
|
38
|
-
resolve: (value: CommitAck) => void;
|
|
42
|
+
resolve: (value: CommitAck | CommitReceiptWire) => void;
|
|
39
43
|
reject: (err: Error) => void;
|
|
40
44
|
timeout: ReturnType<typeof setTimeout>;
|
|
45
|
+
readonly returnReceipt?: boolean;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
/**
|
|
@@ -202,11 +207,14 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
|
|
|
202
207
|
});
|
|
203
208
|
}
|
|
204
209
|
pending.resolve(
|
|
205
|
-
|
|
210
|
+
pending.returnReceipt
|
|
211
|
+
? receipt
|
|
212
|
+
: commitAckSchema.parse({
|
|
206
213
|
status: receipt.status,
|
|
207
214
|
...(receipt.correlationId
|
|
208
215
|
? { correlationId: receipt.correlationId }
|
|
209
216
|
: {}),
|
|
217
|
+
statusAt: receipt.statusAt,
|
|
210
218
|
lastSyncId: receipt.lastSyncId,
|
|
211
219
|
...(receipt.notifications && receipt.notifications.length > 0
|
|
212
220
|
? { notifications: receipt.notifications }
|
|
@@ -214,7 +222,7 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
|
|
|
214
222
|
...(receipt.missingIds && receipt.missingIds.length > 0
|
|
215
223
|
? { missingIds: receipt.missingIds }
|
|
216
224
|
: {}),
|
|
217
|
-
|
|
225
|
+
}),
|
|
218
226
|
);
|
|
219
227
|
} else {
|
|
220
228
|
// Capture the full server error so the caller can see what actually
|
|
@@ -233,6 +241,7 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
|
|
|
233
241
|
code?: unknown;
|
|
234
242
|
message?: unknown;
|
|
235
243
|
request_id?: unknown;
|
|
244
|
+
event_id?: unknown;
|
|
236
245
|
requiredCapability?: unknown;
|
|
237
246
|
details?: unknown;
|
|
238
247
|
};
|
|
@@ -261,6 +270,9 @@ const handleMutationResult: WsFrameHandler = (session, message) => {
|
|
|
261
270
|
!Array.isArray(obj.details)
|
|
262
271
|
? (obj.details as Readonly<Record<string, unknown>>)
|
|
263
272
|
: undefined;
|
|
273
|
+
if (typeof obj.event_id === 'string') {
|
|
274
|
+
details = { ...(details ?? {}), event_id: obj.event_id };
|
|
275
|
+
}
|
|
264
276
|
} else {
|
|
265
277
|
errorMessage = 'mutation failed on server';
|
|
266
278
|
}
|
|
@@ -341,7 +353,12 @@ const handleClaimAck: WsFrameHandler = (session, message) => {
|
|
|
341
353
|
});
|
|
342
354
|
} else {
|
|
343
355
|
const err = error as
|
|
344
|
-
| {
|
|
356
|
+
| {
|
|
357
|
+
code?: unknown;
|
|
358
|
+
message?: unknown;
|
|
359
|
+
request_id?: unknown;
|
|
360
|
+
event_id?: unknown;
|
|
361
|
+
}
|
|
345
362
|
| undefined;
|
|
346
363
|
const code =
|
|
347
364
|
err?.code && typeof err.code === 'string'
|
|
@@ -351,6 +368,11 @@ const handleClaimAck: WsFrameHandler = (session, message) => {
|
|
|
351
368
|
err?.message && typeof err.message === 'string'
|
|
352
369
|
? err.message
|
|
353
370
|
: 'claim rejected by server';
|
|
371
|
+
const requestId =
|
|
372
|
+
typeof err?.request_id === 'string' ? err.request_id : undefined;
|
|
373
|
+
const eventId =
|
|
374
|
+
typeof err?.event_id === 'string' ? err.event_id : undefined;
|
|
375
|
+
const details = eventId === undefined ? undefined : { event_id: eventId };
|
|
354
376
|
// Capability denials get the typed CapabilityError so
|
|
355
377
|
// callers can read `.requiredCapability` and attenuate-
|
|
356
378
|
// and-retry the claim with a narrower token.
|
|
@@ -366,12 +388,23 @@ const handleClaimAck: WsFrameHandler = (session, message) => {
|
|
|
366
388
|
typeof (rc as { scope?: unknown }).scope === 'string'
|
|
367
389
|
? (rc as RequiredCapability)
|
|
368
390
|
: undefined;
|
|
369
|
-
pending.reject(
|
|
391
|
+
pending.reject(
|
|
392
|
+
new CapabilityError(code, msg, requiredCapability, {
|
|
393
|
+
...(requestId !== undefined ? { requestId } : {}),
|
|
394
|
+
...(details !== undefined ? { details } : {}),
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
370
397
|
} else {
|
|
371
398
|
// Route through the shared factory so a failed claim_ack is a
|
|
372
399
|
// typed AbloError (registry code → right subclass), symmetric
|
|
373
400
|
// with the commit `mutation_result` path — never a bare Error.
|
|
374
|
-
pending.reject(
|
|
401
|
+
pending.reject(
|
|
402
|
+
errorFromWire(msg, {
|
|
403
|
+
code,
|
|
404
|
+
requestId,
|
|
405
|
+
details,
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
375
408
|
}
|
|
376
409
|
}
|
|
377
410
|
};
|
|
@@ -405,10 +438,15 @@ const handleSubscriptionAck: WsFrameHandler = (session, message) => {
|
|
|
405
438
|
session.options.syncGroups = ack.syncGroups;
|
|
406
439
|
pending.resolve({ syncGroups: ack.syncGroups });
|
|
407
440
|
} else {
|
|
441
|
+
const eventId = ack.error?.event_id;
|
|
408
442
|
pending.reject(
|
|
409
443
|
errorFromWire(
|
|
410
444
|
ack.error?.message ?? 'update_subscription rejected by server',
|
|
411
|
-
{
|
|
445
|
+
{
|
|
446
|
+
code: ack.error?.code ?? 'malformed_subscription',
|
|
447
|
+
requestId: ack.error?.request_id,
|
|
448
|
+
...(eventId !== undefined ? { details: { event_id: eventId } } : {}),
|
|
449
|
+
},
|
|
412
450
|
),
|
|
413
451
|
);
|
|
414
452
|
}
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
type AuthTokenGetter,
|
|
57
57
|
} from '../auth/credentialSource.js';
|
|
58
58
|
import { buildCommitFrame, type CommitAck, type CommitFrameOperation } from './commitFrames.js';
|
|
59
|
+
import type { CommitReceiptWire } from '../wire/commit.js';
|
|
59
60
|
import {
|
|
60
61
|
dispatchWsFrame,
|
|
61
62
|
readWsInboundFrame,
|
|
@@ -1040,7 +1041,47 @@ export class WsTransport<
|
|
|
1040
1041
|
),
|
|
1041
1042
|
);
|
|
1042
1043
|
}, timeoutMs);
|
|
1043
|
-
this.pendingMutations.set(clientTxId, {
|
|
1044
|
+
this.pendingMutations.set(clientTxId, {
|
|
1045
|
+
resolve: (value) => resolve(value as CommitAck),
|
|
1046
|
+
reject,
|
|
1047
|
+
timeout,
|
|
1048
|
+
});
|
|
1049
|
+
try {
|
|
1050
|
+
const frame = buildCommitFrame(operations, clientTxId, reads, track);
|
|
1051
|
+
this.ws!.send(JSON.stringify(frame));
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
clearTimeout(timeout);
|
|
1054
|
+
this.pendingMutations.delete(clientTxId);
|
|
1055
|
+
reject(toAbloError(error));
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** @internal Exact wire receipt used to correlate a logical commit record. */
|
|
1061
|
+
sendCommitReceipt(
|
|
1062
|
+
operations: readonly CommitFrameOperation[],
|
|
1063
|
+
clientTxId: string,
|
|
1064
|
+
timeoutMs = 15_000,
|
|
1065
|
+
reads?: readonly ReadDependency[] | null,
|
|
1066
|
+
track?: readonly TrackDependency[] | null,
|
|
1067
|
+
): Promise<CommitReceiptWire> {
|
|
1068
|
+
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
1069
|
+
return Promise.reject(this.notConnectedError('commit'));
|
|
1070
|
+
}
|
|
1071
|
+
return new Promise<CommitReceiptWire>((resolve, reject) => {
|
|
1072
|
+
const timeout = setTimeout(() => {
|
|
1073
|
+
this.pendingMutations.delete(clientTxId);
|
|
1074
|
+
reject(new AbloConnectionError(
|
|
1075
|
+
`commit timed out after ${timeoutMs}ms (clientTxId=${clientTxId})`,
|
|
1076
|
+
{ code: 'commit_no_result' },
|
|
1077
|
+
));
|
|
1078
|
+
}, timeoutMs);
|
|
1079
|
+
this.pendingMutations.set(clientTxId, {
|
|
1080
|
+
resolve: (value) => resolve(value as CommitReceiptWire),
|
|
1081
|
+
reject,
|
|
1082
|
+
timeout,
|
|
1083
|
+
returnReceipt: true,
|
|
1084
|
+
});
|
|
1044
1085
|
try {
|
|
1045
1086
|
const frame = buildCommitFrame(operations, clientTxId, reads, track);
|
|
1046
1087
|
this.ws!.send(JSON.stringify(frame));
|
package/src/utils/json.ts
CHANGED
|
@@ -38,7 +38,7 @@ import type { JsonValue } from '../types/streams.js';
|
|
|
38
38
|
* Traversal intentionally creates fresh objects and arrays instead of relying
|
|
39
39
|
* on a framework-specific unwrapping API. That makes Proxy-wrapped plain data
|
|
40
40
|
* (including MobX observables) safe for IndexedDB's structured-clone algorithm
|
|
41
|
-
* while keeping the
|
|
41
|
+
* while keeping the confirmation core independent of any reactive framework.
|
|
42
42
|
*
|
|
43
43
|
* The accepted contract is deliberately narrower than `JSON.stringify`:
|
|
44
44
|
* values that JSON would silently corrupt (`NaN` to `null`, `Map` to `{}`) or
|