@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
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
MAX_READ_SET_ENTRIES,
|
|
13
|
+
readDependencyListSchema,
|
|
14
|
+
readSetProjectionEntryCount,
|
|
15
|
+
trackDependencyListSchema,
|
|
16
|
+
} from '../../coordination/schema.js';
|
|
12
17
|
import { wireCommitOperationSchema } from '../../wire/frames.js';
|
|
13
18
|
import { correlationIdSchema } from '../../wire/commit.js';
|
|
14
19
|
import { idempotencyKeySchema } from './idempotencyKey.js';
|
|
@@ -41,6 +46,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
|
|
|
41
46
|
id: true,
|
|
42
47
|
input: true,
|
|
43
48
|
transactionId: true,
|
|
49
|
+
claimId: true,
|
|
44
50
|
readAt: true,
|
|
45
51
|
onStale: true,
|
|
46
52
|
fenceToken: true,
|
|
@@ -50,6 +56,7 @@ export const durableCommitOperationSchema = wireCommitOperationSchema
|
|
|
50
56
|
id: z.string().min(1),
|
|
51
57
|
input: z.record(z.string(), z.unknown()).optional(),
|
|
52
58
|
transactionId: z.string().min(1).optional(),
|
|
59
|
+
claimId: z.string().min(1).optional(),
|
|
53
60
|
options: z
|
|
54
61
|
.strictObject({
|
|
55
62
|
idempotencyKey: z.string().min(1).max(255).nullable().optional(),
|
|
@@ -62,8 +69,11 @@ export type DurableCommitOperation = z.infer<typeof durableCommitOperationSchema
|
|
|
62
69
|
export type DurableCommitOperationInput = z.input<typeof durableCommitOperationSchema>;
|
|
63
70
|
|
|
64
71
|
const durableCommitOptionsSchema = z.strictObject({
|
|
65
|
-
reads:
|
|
66
|
-
track:
|
|
72
|
+
reads: readDependencyListSchema.nullable().optional(),
|
|
73
|
+
track: trackDependencyListSchema.nullable().optional(),
|
|
74
|
+
}).refine((value) => readSetProjectionEntryCount(value) <= MAX_READ_SET_ENTRIES, {
|
|
75
|
+
path: ['reads'],
|
|
76
|
+
message: `reads and track may contain at most ${MAX_READ_SET_ENTRIES} entries combined`,
|
|
67
77
|
});
|
|
68
78
|
|
|
69
79
|
export const commitOutboxScopeSchema = z.strictObject({
|
|
@@ -53,6 +53,7 @@ export interface CommitFrameOperation {
|
|
|
53
53
|
readonly id: string;
|
|
54
54
|
readonly input?: Record<string, unknown>;
|
|
55
55
|
readonly transactionId?: string;
|
|
56
|
+
readonly claimId?: string | null;
|
|
56
57
|
readonly readAt?: number | null;
|
|
57
58
|
readonly onStale?: OnStaleMode | null;
|
|
58
59
|
readonly fenceToken?: number | null;
|
|
@@ -80,17 +81,18 @@ export function buildCommitFrame(
|
|
|
80
81
|
id: op.id,
|
|
81
82
|
input: op.input,
|
|
82
83
|
transactionId: op.transactionId,
|
|
84
|
+
claimId: op.claimId,
|
|
83
85
|
readAt: op.readAt,
|
|
84
86
|
onStale: op.onStale,
|
|
85
87
|
fenceToken: op.fenceToken,
|
|
86
88
|
})),
|
|
87
89
|
clientTxId,
|
|
88
90
|
};
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
if (track
|
|
91
|
+
// Preserve declaration at the wire boundary. `null`, an explicit empty list,
|
|
92
|
+
// and omission are distinct inputs even when today's server treats each as
|
|
93
|
+
// "none"; the WebSocket path must not silently erase a field the caller set.
|
|
94
|
+
if (reads !== undefined) payload.reads = reads === null ? null : [...reads];
|
|
95
|
+
if (track !== undefined) payload.track = track === null ? null : [...track];
|
|
94
96
|
return { type: 'commit', payload };
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* first successful connect and disposed on teardown.
|
|
16
16
|
*
|
|
17
17
|
* This is a plain state machine, not a reactive object: it lives in the
|
|
18
|
-
*
|
|
18
|
+
* confirmation core, which carries no reactivity runtime (ADR 0016). A consumer
|
|
19
19
|
* that wants to render recovery progress mirrors the transitions through
|
|
20
20
|
* {@link ConnectionCallbacks.onStateChange} into its own observable state —
|
|
21
21
|
* which is what the reactive engine's store does.
|
|
@@ -25,6 +25,7 @@ import { modelWireNames } from '../auth/capability.js';
|
|
|
25
25
|
import type { HttpClientConfig } from './httpOptions.js';
|
|
26
26
|
import type {
|
|
27
27
|
CommitResource,
|
|
28
|
+
CommitReceipt,
|
|
28
29
|
HttpClaimApi,
|
|
29
30
|
HttpClaimsResource,
|
|
30
31
|
HttpLogsResource,
|
|
@@ -46,8 +47,25 @@ import type {
|
|
|
46
47
|
} from '../resources/modelOperations.js';
|
|
47
48
|
import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
|
|
48
49
|
import { omittedModelError } from '../schema/select.js';
|
|
49
|
-
import
|
|
50
|
+
import {
|
|
51
|
+
reconcileFunctionalUpdate,
|
|
52
|
+
type ModelUpdater,
|
|
53
|
+
type FunctionalUpdateOptions,
|
|
54
|
+
} from '../resources/functionalUpdate.js';
|
|
55
|
+
import type { HeldClaim } from '../types/streams.js';
|
|
50
56
|
import { AbloConnectionError, AbloValidationError } from '../errors.js';
|
|
57
|
+
import type { OnStaleMode, ReadDependency } from '../coordination/schema.js';
|
|
58
|
+
import {
|
|
59
|
+
abortReadSetCommit,
|
|
60
|
+
capturePointRead,
|
|
61
|
+
createReadSetContext,
|
|
62
|
+
consumeReadSet,
|
|
63
|
+
kReadEvidence,
|
|
64
|
+
prepareReadSet,
|
|
65
|
+
type ReadSetContext,
|
|
66
|
+
} from '../readSetContext.js';
|
|
67
|
+
import { recordHttpCommitReceipt } from '../commitRecordRuntime.js';
|
|
68
|
+
import type { EffectiveAuthority } from '../auth/capability.js';
|
|
51
69
|
|
|
52
70
|
export interface AbloHttpClientOptions<S extends SchemaRecord>
|
|
53
71
|
extends HttpClientConfig<S> {
|
|
@@ -59,6 +77,18 @@ export interface AbloHttpClientOptions<S extends SchemaRecord>
|
|
|
59
77
|
readonly timeoutMs?: number;
|
|
60
78
|
}
|
|
61
79
|
|
|
80
|
+
declare const capturedRowBrand: unique symbol;
|
|
81
|
+
|
|
82
|
+
/** A point-read row whose exact watermark is privately retained by its client. */
|
|
83
|
+
export type CapturedRow<T = unknown> = T & {
|
|
84
|
+
readonly [capturedRowBrand]: true;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Exact returned rows or low-level canonical dependencies accepted by `reads`. */
|
|
88
|
+
export type HttpModelMutationParams<P> = Omit<P, 'reads'> & {
|
|
89
|
+
readonly reads?: readonly (ReadDependency | CapturedRow)[] | null;
|
|
90
|
+
};
|
|
91
|
+
|
|
62
92
|
/**
|
|
63
93
|
* The per-model surface of the stateless HTTP client — everything reachable over
|
|
64
94
|
* request/response: reads (`get` and `list`), writes (`create`, `update`,
|
|
@@ -91,14 +121,14 @@ export interface HttpModelClient<T, C = T> {
|
|
|
91
121
|
* to what is already here.
|
|
92
122
|
*/
|
|
93
123
|
/** Canonical authoritative point lookup. A miss resolves to `undefined`. */
|
|
94
|
-
get(params: ModelRetrieveParams & ModelReadOptions): Promise<T | undefined>;
|
|
124
|
+
get(params: ModelRetrieveParams & ModelReadOptions): Promise<CapturedRow<T> | undefined>;
|
|
95
125
|
/** @deprecated Use `get({ id })` for an authoritative point lookup. */
|
|
96
|
-
retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<T | undefined>;
|
|
126
|
+
retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<CapturedRow<T> | undefined>;
|
|
97
127
|
/**
|
|
98
128
|
* Reads the rows matching a filter. Same resolution as `get`, in bulk,
|
|
99
129
|
* and deduplicated so concurrent identical calls share one request.
|
|
100
130
|
*/
|
|
101
|
-
list(options?: ServerReadOptions<T>): Promise<T[]>;
|
|
131
|
+
list(options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]>;
|
|
102
132
|
/**
|
|
103
133
|
* Creates a row and returns it, including any framework-applied defaults.
|
|
104
134
|
* Passing an id that already exists is idempotent: the existing row is
|
|
@@ -110,8 +140,8 @@ export interface HttpModelClient<T, C = T> {
|
|
|
110
140
|
* back, so it resolves on the server's confirmation instead. The same is true
|
|
111
141
|
* of `update` and `delete`.
|
|
112
142
|
*/
|
|
113
|
-
create(params: ModelCreateParams<T, C
|
|
114
|
-
update(params: ModelUpdateParams<T, C
|
|
143
|
+
create(params: HttpModelMutationParams<ModelCreateParams<T, C>>): Promise<T>;
|
|
144
|
+
update(params: HttpModelMutationParams<ModelUpdateParams<T, C>>): Promise<T>;
|
|
115
145
|
/**
|
|
116
146
|
* Updates a row with a function of its latest value — `update(id, current =>
|
|
117
147
|
* next)`, the data-layer equivalent of a `setState(prev => next)` reducer. The
|
|
@@ -124,9 +154,9 @@ export interface HttpModelClient<T, C = T> {
|
|
|
124
154
|
update(
|
|
125
155
|
id: string,
|
|
126
156
|
updater: ModelUpdater<T>,
|
|
127
|
-
options?:
|
|
157
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
128
158
|
): Promise<T | undefined>;
|
|
129
|
-
delete(params: ModelDeleteParams<T, C
|
|
159
|
+
delete(params: HttpModelMutationParams<ModelDeleteParams<T, C>>): Promise<void>;
|
|
130
160
|
claim: HttpClaimApi<T, C>;
|
|
131
161
|
/**
|
|
132
162
|
* Registers a durable premise on a row — "keep telling me about this one".
|
|
@@ -164,7 +194,7 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
164
194
|
} & {
|
|
165
195
|
/** Runs one-time setup (durable-outbox scope resolution and replay) before the client is used. It also runs lazily ahead of the first request, so calling it yourself is optional. */
|
|
166
196
|
ready(): Promise<void>;
|
|
167
|
-
/** Replays every pending durable HTTP write in seal order and waits for
|
|
197
|
+
/** Replays every pending durable HTTP write in seal order and waits for confirmation. */
|
|
168
198
|
waitForFlush(): Promise<void>;
|
|
169
199
|
readonly commits: CommitResource;
|
|
170
200
|
/**
|
|
@@ -177,6 +207,8 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
177
207
|
readonly claims: HttpClaimsResource;
|
|
178
208
|
/** Durable, credential-scoped authoritative event pages. */
|
|
179
209
|
readonly logs: HttpLogsResource;
|
|
210
|
+
/** Server-confirmed authority of the credential, populated by `ready()`. */
|
|
211
|
+
readonly identity: EffectiveAuthority | null;
|
|
180
212
|
dispose(): Promise<void>;
|
|
181
213
|
/** Resolves the bearer credential this client authenticates with, or `null` if none is set. */
|
|
182
214
|
getAuthToken(): Promise<string | null>;
|
|
@@ -200,6 +232,7 @@ const PROTOCOL_MEMBERS = new Set<string>([
|
|
|
200
232
|
'commits',
|
|
201
233
|
'claims',
|
|
202
234
|
'logs',
|
|
235
|
+
'identity',
|
|
203
236
|
'getAuthToken',
|
|
204
237
|
'sessions',
|
|
205
238
|
]);
|
|
@@ -217,9 +250,13 @@ function isProtocolMember(prop: string): prop is keyof HttpTransport {
|
|
|
217
250
|
*/
|
|
218
251
|
function createHttpModelClient<T, C = T>(
|
|
219
252
|
protocol: HttpTransportModel<T, C>,
|
|
253
|
+
modelName: string,
|
|
254
|
+
clientIdentity: object,
|
|
255
|
+
readSetContext: ReadSetContext | undefined,
|
|
220
256
|
): HttpModelClient<T, C> {
|
|
221
257
|
async function readRow(id: string): Promise<T | undefined> {
|
|
222
258
|
const read = await protocol.get({ id });
|
|
259
|
+
capturePointRead(readSetContext, clientIdentity, modelName, id, read.data, read.stamp);
|
|
223
260
|
return read.data;
|
|
224
261
|
}
|
|
225
262
|
|
|
@@ -248,16 +285,48 @@ function createHttpModelClient<T, C = T>(
|
|
|
248
285
|
};
|
|
249
286
|
}
|
|
250
287
|
|
|
251
|
-
function
|
|
288
|
+
function preparedMutation<P extends {
|
|
289
|
+
readonly claim?: unknown;
|
|
290
|
+
readonly idempotencyKey?: string | null;
|
|
291
|
+
readonly readAt?: number | null;
|
|
292
|
+
readonly onStale?: OnStaleMode | null;
|
|
293
|
+
readonly reads?: readonly unknown[] | null;
|
|
294
|
+
}>(
|
|
295
|
+
params: P,
|
|
296
|
+
) {
|
|
297
|
+
const prepared = prepareReadSet(
|
|
298
|
+
readSetContext,
|
|
299
|
+
clientIdentity,
|
|
300
|
+
params.readAt,
|
|
301
|
+
params.onStale,
|
|
302
|
+
params.idempotencyKey,
|
|
303
|
+
params.reads,
|
|
304
|
+
);
|
|
305
|
+
return {
|
|
306
|
+
options: {
|
|
307
|
+
...mutationOptions(params),
|
|
308
|
+
...(prepared.readAt !== undefined ? { readAt: prepared.readAt } : {}),
|
|
309
|
+
...(prepared.onStale !== undefined ? { onStale: prepared.onStale } : {}),
|
|
310
|
+
...(prepared.reads !== undefined ? { reads: prepared.reads } : {}),
|
|
311
|
+
...(prepared.idempotencyKey !== undefined
|
|
312
|
+
? { idempotencyKey: prepared.idempotencyKey }
|
|
313
|
+
: {}),
|
|
314
|
+
},
|
|
315
|
+
consumed: prepared.consumed,
|
|
316
|
+
automaticCommit: prepared.automaticCommit,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function update(params: HttpModelMutationParams<ModelUpdateParams<T, C>>): Promise<T>;
|
|
252
321
|
function update(
|
|
253
322
|
id: string,
|
|
254
323
|
updater: ModelUpdater<T>,
|
|
255
|
-
options?:
|
|
324
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
256
325
|
): Promise<T | undefined>;
|
|
257
326
|
async function update(
|
|
258
|
-
arg: ModelUpdateParams<T, C
|
|
327
|
+
arg: HttpModelMutationParams<ModelUpdateParams<T, C>> | string,
|
|
259
328
|
updater?: ModelUpdater<T>,
|
|
260
|
-
options?:
|
|
329
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
261
330
|
): Promise<T | undefined> {
|
|
262
331
|
if (typeof arg === 'string') {
|
|
263
332
|
if (!updater) {
|
|
@@ -266,38 +335,186 @@ function createHttpModelClient<T, C = T>(
|
|
|
266
335
|
{ code: 'write_options_invalid' },
|
|
267
336
|
);
|
|
268
337
|
}
|
|
269
|
-
const receipt = await
|
|
338
|
+
const receipt = await reconcileFunctionalUpdate<
|
|
339
|
+
T,
|
|
340
|
+
CommitReceipt,
|
|
341
|
+
ReadDependency | CapturedRow
|
|
342
|
+
>(
|
|
343
|
+
updater,
|
|
344
|
+
options,
|
|
345
|
+
{
|
|
346
|
+
model: modelName,
|
|
347
|
+
id: arg,
|
|
348
|
+
readFresh: async () => {
|
|
349
|
+
const read = await protocol.get({ id: arg });
|
|
350
|
+
capturePointRead(
|
|
351
|
+
readSetContext,
|
|
352
|
+
clientIdentity,
|
|
353
|
+
modelName,
|
|
354
|
+
arg,
|
|
355
|
+
read.data,
|
|
356
|
+
read.stamp,
|
|
357
|
+
);
|
|
358
|
+
return { data: read.data, stamp: read.stamp };
|
|
359
|
+
},
|
|
360
|
+
writeNext: async (patch, readAt) => {
|
|
361
|
+
const prepared = preparedMutation(
|
|
362
|
+
{
|
|
363
|
+
readAt,
|
|
364
|
+
onStale: 'reject' as const,
|
|
365
|
+
reads: options?.reads,
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
try {
|
|
369
|
+
const result = await protocol.update({
|
|
370
|
+
...prepared.options,
|
|
371
|
+
id: arg,
|
|
372
|
+
data: patch,
|
|
373
|
+
});
|
|
374
|
+
consumeReadSet(
|
|
375
|
+
readSetContext,
|
|
376
|
+
clientIdentity,
|
|
377
|
+
prepared.consumed,
|
|
378
|
+
prepared.automaticCommit,
|
|
379
|
+
);
|
|
380
|
+
return result;
|
|
381
|
+
} catch (error) {
|
|
382
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
);
|
|
270
388
|
return receipt === undefined ? undefined : requireUpdatedRow(arg);
|
|
271
389
|
}
|
|
272
390
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
391
|
+
const prepared = preparedMutation(arg);
|
|
392
|
+
try {
|
|
393
|
+
await protocol.update({
|
|
394
|
+
...prepared.options,
|
|
395
|
+
id: arg.id,
|
|
396
|
+
data: arg.data,
|
|
397
|
+
});
|
|
398
|
+
} catch (error) {
|
|
399
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
consumeReadSet(
|
|
403
|
+
readSetContext,
|
|
404
|
+
clientIdentity,
|
|
405
|
+
prepared.consumed,
|
|
406
|
+
prepared.automaticCommit,
|
|
407
|
+
);
|
|
277
408
|
return requireUpdatedRow(arg.id);
|
|
278
409
|
}
|
|
279
410
|
|
|
280
|
-
const get = async (
|
|
411
|
+
const get = async (
|
|
412
|
+
params: ModelRetrieveParams & ModelReadOptions,
|
|
413
|
+
): Promise<CapturedRow<T> | undefined> => {
|
|
281
414
|
const read = await protocol.get(params);
|
|
282
|
-
|
|
415
|
+
capturePointRead(
|
|
416
|
+
readSetContext,
|
|
417
|
+
clientIdentity,
|
|
418
|
+
modelName,
|
|
419
|
+
params.id,
|
|
420
|
+
read.data,
|
|
421
|
+
read.stamp,
|
|
422
|
+
);
|
|
423
|
+
return read.data as CapturedRow<T> | undefined;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const list = async (options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]> => {
|
|
427
|
+
const snapshot = await protocol.list(options);
|
|
428
|
+
const registry = readSetContext?.getStore();
|
|
429
|
+
if (!registry) return [...snapshot.data] as CapturedRow<T>[];
|
|
430
|
+
if (!snapshot.evidence) {
|
|
431
|
+
throw new AbloConnectionError(
|
|
432
|
+
`${modelName}.list did not return row evidence. Upgrade the Ablo server or use get({ id }).`,
|
|
433
|
+
{ code: 'commit_no_result' },
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const byId = new Map(snapshot.evidence.map((entry) => [entry.id, entry.stamp]));
|
|
437
|
+
for (const row of snapshot.data) {
|
|
438
|
+
const id = typeof row === 'object' && row !== null
|
|
439
|
+
? Reflect.get(row as object, 'id')
|
|
440
|
+
: undefined;
|
|
441
|
+
const stamp = typeof id === 'string' ? byId.get(id) : undefined;
|
|
442
|
+
if (typeof id !== 'string' || stamp === undefined) {
|
|
443
|
+
throw new AbloConnectionError(
|
|
444
|
+
`${modelName}.list returned a row without matching evidence.`,
|
|
445
|
+
{ code: 'commit_no_result' },
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
capturePointRead(readSetContext, clientIdentity, modelName, id, row, stamp);
|
|
449
|
+
}
|
|
450
|
+
return [...snapshot.data] as CapturedRow<T>[];
|
|
283
451
|
};
|
|
284
452
|
|
|
453
|
+
// Claim acquisition performs its authoritative read only after the grant.
|
|
454
|
+
// Preserve that exact post-grant watermark while retaining the callable
|
|
455
|
+
// namespace and all of its state/queue/release members through the proxy.
|
|
456
|
+
const claim = new Proxy(protocol.claim, {
|
|
457
|
+
apply(target, thisArg, argArray) {
|
|
458
|
+
const result = Reflect.apply(target, thisArg, argArray) as Promise<HeldClaim<T> | null>;
|
|
459
|
+
return result.then((held) => {
|
|
460
|
+
if (held && held.readAt !== undefined) {
|
|
461
|
+
capturePointRead(
|
|
462
|
+
readSetContext,
|
|
463
|
+
clientIdentity,
|
|
464
|
+
modelName,
|
|
465
|
+
held.target.id,
|
|
466
|
+
held.data,
|
|
467
|
+
held.readAt,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return held;
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
});
|
|
474
|
+
|
|
285
475
|
return {
|
|
286
476
|
get,
|
|
287
477
|
retrieve: get,
|
|
288
|
-
list
|
|
478
|
+
list,
|
|
289
479
|
async create(params): Promise<T> {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
480
|
+
const id = params.id ?? '';
|
|
481
|
+
const prepared = preparedMutation(params);
|
|
482
|
+
let row: T;
|
|
483
|
+
try {
|
|
484
|
+
row = await protocol.create({
|
|
485
|
+
...prepared.options,
|
|
486
|
+
...(params.id !== undefined ? { id: params.id } : {}),
|
|
487
|
+
data: params.data as Record<string, unknown>,
|
|
488
|
+
});
|
|
489
|
+
} catch (error) {
|
|
490
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
consumeReadSet(
|
|
494
|
+
readSetContext,
|
|
495
|
+
clientIdentity,
|
|
496
|
+
prepared.consumed,
|
|
497
|
+
prepared.automaticCommit,
|
|
498
|
+
);
|
|
294
499
|
return row;
|
|
295
500
|
},
|
|
296
501
|
update,
|
|
297
502
|
async delete(params): Promise<void> {
|
|
298
|
-
|
|
503
|
+
const prepared = preparedMutation(params);
|
|
504
|
+
try {
|
|
505
|
+
await protocol.delete({ ...prepared.options, id: params.id });
|
|
506
|
+
} catch (error) {
|
|
507
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
consumeReadSet(
|
|
511
|
+
readSetContext,
|
|
512
|
+
clientIdentity,
|
|
513
|
+
prepared.consumed,
|
|
514
|
+
prepared.automaticCommit,
|
|
515
|
+
);
|
|
299
516
|
},
|
|
300
|
-
claim
|
|
517
|
+
claim,
|
|
301
518
|
track: (params) => protocol.track(params),
|
|
302
519
|
};
|
|
303
520
|
}
|
|
@@ -312,14 +529,20 @@ function createHttpModelClient<T, C = T>(
|
|
|
312
529
|
export function createAbloHttpClient<S extends SchemaRecord>(
|
|
313
530
|
options: AbloHttpClientOptions<S>,
|
|
314
531
|
): AbloHttpClient<S> {
|
|
315
|
-
const { schema, ...rest } = options;
|
|
532
|
+
const { schema, onCommitReceipt, ...rest } = options;
|
|
533
|
+
const readSetContext = createReadSetContext();
|
|
316
534
|
const transport: HttpTransport = createHttpTransport({
|
|
317
535
|
...rest,
|
|
318
536
|
// Derived from the schema this client is bound to, never assembled by hand
|
|
319
537
|
// — see `auth/capability.ts`.
|
|
320
538
|
modelTypenames: modelWireNames(schema.models),
|
|
539
|
+
onCommitReceipt: (observation) => {
|
|
540
|
+
recordHttpCommitReceipt(readSetContext, observation);
|
|
541
|
+
onCommitReceipt?.(observation);
|
|
542
|
+
},
|
|
321
543
|
});
|
|
322
544
|
const schemaModels = new Set(Object.keys(schema.models));
|
|
545
|
+
const clientIdentity = {};
|
|
323
546
|
const omittedModels = new Set(schema.omittedModels ?? []);
|
|
324
547
|
const models = new Map<
|
|
325
548
|
string,
|
|
@@ -331,13 +554,21 @@ export function createAbloHttpClient<S extends SchemaRecord>(
|
|
|
331
554
|
): HttpModelClient<Record<string, unknown>, Record<string, unknown>> => {
|
|
332
555
|
const cached = models.get(name);
|
|
333
556
|
if (cached) return cached;
|
|
334
|
-
const created = createHttpModelClient(
|
|
557
|
+
const created = createHttpModelClient(
|
|
558
|
+
transport.model(name),
|
|
559
|
+
name,
|
|
560
|
+
clientIdentity,
|
|
561
|
+
readSetContext,
|
|
562
|
+
);
|
|
335
563
|
models.set(name, created);
|
|
336
564
|
return created;
|
|
337
565
|
};
|
|
338
566
|
|
|
339
567
|
const facade = new Proxy<Partial<AbloHttpClient<S>>>({}, {
|
|
340
568
|
get(_target, prop) {
|
|
569
|
+
if (prop === kReadEvidence) {
|
|
570
|
+
return { context: readSetContext, client: clientIdentity };
|
|
571
|
+
}
|
|
341
572
|
if (typeof prop !== 'string') return undefined;
|
|
342
573
|
// Real protocol members pass through unchanged.
|
|
343
574
|
if (isProtocolMember(prop)) {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import type { Schema, SchemaRecord } from '../schema/schema.js';
|
|
14
14
|
import type { AuthClientOptions } from '../auth/apiKey.js';
|
|
15
15
|
import type { CoordinationObservability } from '../observability.js';
|
|
16
|
-
import type { CommitOutboxScope } from '../transactions/
|
|
16
|
+
import type { CommitOutboxScope } from '../transactions/confirmation/commitEnvelope.js';
|
|
17
|
+
import type { CommitReceiptWire } from '../wire/commit.js';
|
|
17
18
|
import type {
|
|
18
19
|
DurableWriteStore,
|
|
19
20
|
DurableWritesConfig,
|
|
@@ -38,4 +39,11 @@ export interface HttpClientConfig<S extends SchemaRecord = SchemaRecord>
|
|
|
38
39
|
commitOutboxScope?: CommitOutboxScope;
|
|
39
40
|
/** Which transport carries this client's traffic. */
|
|
40
41
|
transport?: 'websocket' | 'http' | undefined;
|
|
42
|
+
/** @internal Exact receipt phases observed while dispatching one HTTP commit. */
|
|
43
|
+
onCommitReceipt?: (observation: {
|
|
44
|
+
readonly receipt: CommitReceiptWire;
|
|
45
|
+
readonly method: string;
|
|
46
|
+
readonly path: string;
|
|
47
|
+
readonly body: unknown;
|
|
48
|
+
}) => void;
|
|
41
49
|
}
|