@abloatai/transaction 0.48.0 → 0.49.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 +54 -0
- package/dist/readSetContext.d.ts.map +1 -0
- package/dist/readSetContext.js +98 -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 +14 -2
- 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 +134 -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 +18 -0
- package/src/logger.ts +1 -1
- package/src/observability.ts +1 -1
- package/src/readSetContext.ts +164 -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 +14 -2
- 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 +256 -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
|
@@ -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,24 @@ 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
|
+
prepareReadSet,
|
|
64
|
+
type ReadSetContext,
|
|
65
|
+
} from '../readSetContext.js';
|
|
66
|
+
import { recordHttpCommitReceipt } from '../commitRecordRuntime.js';
|
|
67
|
+
import type { EffectiveAuthority } from '../auth/capability.js';
|
|
51
68
|
|
|
52
69
|
export interface AbloHttpClientOptions<S extends SchemaRecord>
|
|
53
70
|
extends HttpClientConfig<S> {
|
|
@@ -59,6 +76,18 @@ export interface AbloHttpClientOptions<S extends SchemaRecord>
|
|
|
59
76
|
readonly timeoutMs?: number;
|
|
60
77
|
}
|
|
61
78
|
|
|
79
|
+
declare const capturedRowBrand: unique symbol;
|
|
80
|
+
|
|
81
|
+
/** A point-read row whose exact watermark is privately retained by its client. */
|
|
82
|
+
export type CapturedRow<T = unknown> = T & {
|
|
83
|
+
readonly [capturedRowBrand]: true;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Exact returned rows or low-level canonical dependencies accepted by `reads`. */
|
|
87
|
+
export type HttpModelMutationParams<P> = Omit<P, 'reads'> & {
|
|
88
|
+
readonly reads?: readonly (ReadDependency | CapturedRow)[] | null;
|
|
89
|
+
};
|
|
90
|
+
|
|
62
91
|
/**
|
|
63
92
|
* The per-model surface of the stateless HTTP client — everything reachable over
|
|
64
93
|
* request/response: reads (`get` and `list`), writes (`create`, `update`,
|
|
@@ -91,14 +120,14 @@ export interface HttpModelClient<T, C = T> {
|
|
|
91
120
|
* to what is already here.
|
|
92
121
|
*/
|
|
93
122
|
/** Canonical authoritative point lookup. A miss resolves to `undefined`. */
|
|
94
|
-
get(params: ModelRetrieveParams & ModelReadOptions): Promise<T | undefined>;
|
|
123
|
+
get(params: ModelRetrieveParams & ModelReadOptions): Promise<CapturedRow<T> | undefined>;
|
|
95
124
|
/** @deprecated Use `get({ id })` for an authoritative point lookup. */
|
|
96
|
-
retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<T | undefined>;
|
|
125
|
+
retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<CapturedRow<T> | undefined>;
|
|
97
126
|
/**
|
|
98
127
|
* Reads the rows matching a filter. Same resolution as `get`, in bulk,
|
|
99
128
|
* and deduplicated so concurrent identical calls share one request.
|
|
100
129
|
*/
|
|
101
|
-
list(options?: ServerReadOptions<T>): Promise<T[]>;
|
|
130
|
+
list(options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]>;
|
|
102
131
|
/**
|
|
103
132
|
* Creates a row and returns it, including any framework-applied defaults.
|
|
104
133
|
* Passing an id that already exists is idempotent: the existing row is
|
|
@@ -110,8 +139,8 @@ export interface HttpModelClient<T, C = T> {
|
|
|
110
139
|
* back, so it resolves on the server's confirmation instead. The same is true
|
|
111
140
|
* of `update` and `delete`.
|
|
112
141
|
*/
|
|
113
|
-
create(params: ModelCreateParams<T, C
|
|
114
|
-
update(params: ModelUpdateParams<T, C
|
|
142
|
+
create(params: HttpModelMutationParams<ModelCreateParams<T, C>>): Promise<T>;
|
|
143
|
+
update(params: HttpModelMutationParams<ModelUpdateParams<T, C>>): Promise<T>;
|
|
115
144
|
/**
|
|
116
145
|
* Updates a row with a function of its latest value — `update(id, current =>
|
|
117
146
|
* next)`, the data-layer equivalent of a `setState(prev => next)` reducer. The
|
|
@@ -124,9 +153,9 @@ export interface HttpModelClient<T, C = T> {
|
|
|
124
153
|
update(
|
|
125
154
|
id: string,
|
|
126
155
|
updater: ModelUpdater<T>,
|
|
127
|
-
options?:
|
|
156
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
128
157
|
): Promise<T | undefined>;
|
|
129
|
-
delete(params: ModelDeleteParams<T, C
|
|
158
|
+
delete(params: HttpModelMutationParams<ModelDeleteParams<T, C>>): Promise<void>;
|
|
130
159
|
claim: HttpClaimApi<T, C>;
|
|
131
160
|
/**
|
|
132
161
|
* Registers a durable premise on a row — "keep telling me about this one".
|
|
@@ -164,7 +193,7 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
164
193
|
} & {
|
|
165
194
|
/** 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
195
|
ready(): Promise<void>;
|
|
167
|
-
/** Replays every pending durable HTTP write in seal order and waits for
|
|
196
|
+
/** Replays every pending durable HTTP write in seal order and waits for confirmation. */
|
|
168
197
|
waitForFlush(): Promise<void>;
|
|
169
198
|
readonly commits: CommitResource;
|
|
170
199
|
/**
|
|
@@ -177,6 +206,8 @@ export type AbloHttpClient<S extends SchemaRecord> = {
|
|
|
177
206
|
readonly claims: HttpClaimsResource;
|
|
178
207
|
/** Durable, credential-scoped authoritative event pages. */
|
|
179
208
|
readonly logs: HttpLogsResource;
|
|
209
|
+
/** Server-confirmed authority of the credential, populated by `ready()`. */
|
|
210
|
+
readonly identity: EffectiveAuthority | null;
|
|
180
211
|
dispose(): Promise<void>;
|
|
181
212
|
/** Resolves the bearer credential this client authenticates with, or `null` if none is set. */
|
|
182
213
|
getAuthToken(): Promise<string | null>;
|
|
@@ -200,6 +231,7 @@ const PROTOCOL_MEMBERS = new Set<string>([
|
|
|
200
231
|
'commits',
|
|
201
232
|
'claims',
|
|
202
233
|
'logs',
|
|
234
|
+
'identity',
|
|
203
235
|
'getAuthToken',
|
|
204
236
|
'sessions',
|
|
205
237
|
]);
|
|
@@ -217,9 +249,13 @@ function isProtocolMember(prop: string): prop is keyof HttpTransport {
|
|
|
217
249
|
*/
|
|
218
250
|
function createHttpModelClient<T, C = T>(
|
|
219
251
|
protocol: HttpTransportModel<T, C>,
|
|
252
|
+
modelName: string,
|
|
253
|
+
clientIdentity: object,
|
|
254
|
+
readSetContext: ReadSetContext | undefined,
|
|
220
255
|
): HttpModelClient<T, C> {
|
|
221
256
|
async function readRow(id: string): Promise<T | undefined> {
|
|
222
257
|
const read = await protocol.get({ id });
|
|
258
|
+
capturePointRead(readSetContext, clientIdentity, modelName, id, read.data, read.stamp);
|
|
223
259
|
return read.data;
|
|
224
260
|
}
|
|
225
261
|
|
|
@@ -248,16 +284,48 @@ function createHttpModelClient<T, C = T>(
|
|
|
248
284
|
};
|
|
249
285
|
}
|
|
250
286
|
|
|
251
|
-
function
|
|
287
|
+
function preparedMutation<P extends {
|
|
288
|
+
readonly claim?: unknown;
|
|
289
|
+
readonly idempotencyKey?: string | null;
|
|
290
|
+
readonly readAt?: number | null;
|
|
291
|
+
readonly onStale?: OnStaleMode | null;
|
|
292
|
+
readonly reads?: readonly unknown[] | null;
|
|
293
|
+
}>(
|
|
294
|
+
params: P,
|
|
295
|
+
) {
|
|
296
|
+
const prepared = prepareReadSet(
|
|
297
|
+
readSetContext,
|
|
298
|
+
clientIdentity,
|
|
299
|
+
params.readAt,
|
|
300
|
+
params.onStale,
|
|
301
|
+
params.idempotencyKey,
|
|
302
|
+
params.reads,
|
|
303
|
+
);
|
|
304
|
+
return {
|
|
305
|
+
options: {
|
|
306
|
+
...mutationOptions(params),
|
|
307
|
+
...(prepared.readAt !== undefined ? { readAt: prepared.readAt } : {}),
|
|
308
|
+
...(prepared.onStale !== undefined ? { onStale: prepared.onStale } : {}),
|
|
309
|
+
...(prepared.reads !== undefined ? { reads: prepared.reads } : {}),
|
|
310
|
+
...(prepared.idempotencyKey !== undefined
|
|
311
|
+
? { idempotencyKey: prepared.idempotencyKey }
|
|
312
|
+
: {}),
|
|
313
|
+
},
|
|
314
|
+
consumed: prepared.consumed,
|
|
315
|
+
automaticCommit: prepared.automaticCommit,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function update(params: HttpModelMutationParams<ModelUpdateParams<T, C>>): Promise<T>;
|
|
252
320
|
function update(
|
|
253
321
|
id: string,
|
|
254
322
|
updater: ModelUpdater<T>,
|
|
255
|
-
options?:
|
|
323
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
256
324
|
): Promise<T | undefined>;
|
|
257
325
|
async function update(
|
|
258
|
-
arg: ModelUpdateParams<T, C
|
|
326
|
+
arg: HttpModelMutationParams<ModelUpdateParams<T, C>> | string,
|
|
259
327
|
updater?: ModelUpdater<T>,
|
|
260
|
-
options?:
|
|
328
|
+
options?: FunctionalUpdateOptions<ReadDependency | CapturedRow>,
|
|
261
329
|
): Promise<T | undefined> {
|
|
262
330
|
if (typeof arg === 'string') {
|
|
263
331
|
if (!updater) {
|
|
@@ -266,38 +334,186 @@ function createHttpModelClient<T, C = T>(
|
|
|
266
334
|
{ code: 'write_options_invalid' },
|
|
267
335
|
);
|
|
268
336
|
}
|
|
269
|
-
const receipt = await
|
|
337
|
+
const receipt = await reconcileFunctionalUpdate<
|
|
338
|
+
T,
|
|
339
|
+
CommitReceipt,
|
|
340
|
+
ReadDependency | CapturedRow
|
|
341
|
+
>(
|
|
342
|
+
updater,
|
|
343
|
+
options,
|
|
344
|
+
{
|
|
345
|
+
model: modelName,
|
|
346
|
+
id: arg,
|
|
347
|
+
readFresh: async () => {
|
|
348
|
+
const read = await protocol.get({ id: arg });
|
|
349
|
+
capturePointRead(
|
|
350
|
+
readSetContext,
|
|
351
|
+
clientIdentity,
|
|
352
|
+
modelName,
|
|
353
|
+
arg,
|
|
354
|
+
read.data,
|
|
355
|
+
read.stamp,
|
|
356
|
+
);
|
|
357
|
+
return { data: read.data, stamp: read.stamp };
|
|
358
|
+
},
|
|
359
|
+
writeNext: async (patch, readAt) => {
|
|
360
|
+
const prepared = preparedMutation(
|
|
361
|
+
{
|
|
362
|
+
readAt,
|
|
363
|
+
onStale: 'reject' as const,
|
|
364
|
+
reads: options?.reads,
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
try {
|
|
368
|
+
const result = await protocol.update({
|
|
369
|
+
...prepared.options,
|
|
370
|
+
id: arg,
|
|
371
|
+
data: patch,
|
|
372
|
+
});
|
|
373
|
+
consumeReadSet(
|
|
374
|
+
readSetContext,
|
|
375
|
+
clientIdentity,
|
|
376
|
+
prepared.consumed,
|
|
377
|
+
prepared.automaticCommit,
|
|
378
|
+
);
|
|
379
|
+
return result;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
);
|
|
270
387
|
return receipt === undefined ? undefined : requireUpdatedRow(arg);
|
|
271
388
|
}
|
|
272
389
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
390
|
+
const prepared = preparedMutation(arg);
|
|
391
|
+
try {
|
|
392
|
+
await protocol.update({
|
|
393
|
+
...prepared.options,
|
|
394
|
+
id: arg.id,
|
|
395
|
+
data: arg.data,
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
consumeReadSet(
|
|
402
|
+
readSetContext,
|
|
403
|
+
clientIdentity,
|
|
404
|
+
prepared.consumed,
|
|
405
|
+
prepared.automaticCommit,
|
|
406
|
+
);
|
|
277
407
|
return requireUpdatedRow(arg.id);
|
|
278
408
|
}
|
|
279
409
|
|
|
280
|
-
const get = async (
|
|
410
|
+
const get = async (
|
|
411
|
+
params: ModelRetrieveParams & ModelReadOptions,
|
|
412
|
+
): Promise<CapturedRow<T> | undefined> => {
|
|
281
413
|
const read = await protocol.get(params);
|
|
282
|
-
|
|
414
|
+
capturePointRead(
|
|
415
|
+
readSetContext,
|
|
416
|
+
clientIdentity,
|
|
417
|
+
modelName,
|
|
418
|
+
params.id,
|
|
419
|
+
read.data,
|
|
420
|
+
read.stamp,
|
|
421
|
+
);
|
|
422
|
+
return read.data as CapturedRow<T> | undefined;
|
|
283
423
|
};
|
|
284
424
|
|
|
425
|
+
const list = async (options?: ServerReadOptions<T>): Promise<CapturedRow<T>[]> => {
|
|
426
|
+
const snapshot = await protocol.list(options);
|
|
427
|
+
const registry = readSetContext?.getStore();
|
|
428
|
+
if (!registry) return [...snapshot.data] as CapturedRow<T>[];
|
|
429
|
+
if (!snapshot.evidence) {
|
|
430
|
+
throw new AbloConnectionError(
|
|
431
|
+
`${modelName}.list did not return row evidence. Upgrade the Ablo server or use get({ id }).`,
|
|
432
|
+
{ code: 'commit_no_result' },
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const byId = new Map(snapshot.evidence.map((entry) => [entry.id, entry.stamp]));
|
|
436
|
+
for (const row of snapshot.data) {
|
|
437
|
+
const id = typeof row === 'object' && row !== null
|
|
438
|
+
? Reflect.get(row as object, 'id')
|
|
439
|
+
: undefined;
|
|
440
|
+
const stamp = typeof id === 'string' ? byId.get(id) : undefined;
|
|
441
|
+
if (typeof id !== 'string' || stamp === undefined) {
|
|
442
|
+
throw new AbloConnectionError(
|
|
443
|
+
`${modelName}.list returned a row without matching evidence.`,
|
|
444
|
+
{ code: 'commit_no_result' },
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
capturePointRead(readSetContext, clientIdentity, modelName, id, row, stamp);
|
|
448
|
+
}
|
|
449
|
+
return [...snapshot.data] as CapturedRow<T>[];
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Claim acquisition performs its authoritative read only after the grant.
|
|
453
|
+
// Preserve that exact post-grant watermark while retaining the callable
|
|
454
|
+
// namespace and all of its state/queue/release members through the proxy.
|
|
455
|
+
const claim = new Proxy(protocol.claim, {
|
|
456
|
+
apply(target, thisArg, argArray) {
|
|
457
|
+
const result = Reflect.apply(target, thisArg, argArray) as Promise<HeldClaim<T> | null>;
|
|
458
|
+
return result.then((held) => {
|
|
459
|
+
if (held && held.readAt !== undefined) {
|
|
460
|
+
capturePointRead(
|
|
461
|
+
readSetContext,
|
|
462
|
+
clientIdentity,
|
|
463
|
+
modelName,
|
|
464
|
+
held.target.id,
|
|
465
|
+
held.data,
|
|
466
|
+
held.readAt,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return held;
|
|
470
|
+
});
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
285
474
|
return {
|
|
286
475
|
get,
|
|
287
476
|
retrieve: get,
|
|
288
|
-
list
|
|
477
|
+
list,
|
|
289
478
|
async create(params): Promise<T> {
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
479
|
+
const id = params.id ?? '';
|
|
480
|
+
const prepared = preparedMutation(params);
|
|
481
|
+
let row: T;
|
|
482
|
+
try {
|
|
483
|
+
row = await protocol.create({
|
|
484
|
+
...prepared.options,
|
|
485
|
+
...(params.id !== undefined ? { id: params.id } : {}),
|
|
486
|
+
data: params.data as Record<string, unknown>,
|
|
487
|
+
});
|
|
488
|
+
} catch (error) {
|
|
489
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
consumeReadSet(
|
|
493
|
+
readSetContext,
|
|
494
|
+
clientIdentity,
|
|
495
|
+
prepared.consumed,
|
|
496
|
+
prepared.automaticCommit,
|
|
497
|
+
);
|
|
294
498
|
return row;
|
|
295
499
|
},
|
|
296
500
|
update,
|
|
297
501
|
async delete(params): Promise<void> {
|
|
298
|
-
|
|
502
|
+
const prepared = preparedMutation(params);
|
|
503
|
+
try {
|
|
504
|
+
await protocol.delete({ ...prepared.options, id: params.id });
|
|
505
|
+
} catch (error) {
|
|
506
|
+
abortReadSetCommit(readSetContext, prepared.automaticCommit);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
consumeReadSet(
|
|
510
|
+
readSetContext,
|
|
511
|
+
clientIdentity,
|
|
512
|
+
prepared.consumed,
|
|
513
|
+
prepared.automaticCommit,
|
|
514
|
+
);
|
|
299
515
|
},
|
|
300
|
-
claim
|
|
516
|
+
claim,
|
|
301
517
|
track: (params) => protocol.track(params),
|
|
302
518
|
};
|
|
303
519
|
}
|
|
@@ -312,14 +528,20 @@ function createHttpModelClient<T, C = T>(
|
|
|
312
528
|
export function createAbloHttpClient<S extends SchemaRecord>(
|
|
313
529
|
options: AbloHttpClientOptions<S>,
|
|
314
530
|
): AbloHttpClient<S> {
|
|
315
|
-
const { schema, ...rest } = options;
|
|
531
|
+
const { schema, onCommitReceipt, ...rest } = options;
|
|
532
|
+
const readSetContext = createReadSetContext();
|
|
316
533
|
const transport: HttpTransport = createHttpTransport({
|
|
317
534
|
...rest,
|
|
318
535
|
// Derived from the schema this client is bound to, never assembled by hand
|
|
319
536
|
// — see `auth/capability.ts`.
|
|
320
537
|
modelTypenames: modelWireNames(schema.models),
|
|
538
|
+
onCommitReceipt: (observation) => {
|
|
539
|
+
recordHttpCommitReceipt(readSetContext, observation);
|
|
540
|
+
onCommitReceipt?.(observation);
|
|
541
|
+
},
|
|
321
542
|
});
|
|
322
543
|
const schemaModels = new Set(Object.keys(schema.models));
|
|
544
|
+
const clientIdentity = {};
|
|
323
545
|
const omittedModels = new Set(schema.omittedModels ?? []);
|
|
324
546
|
const models = new Map<
|
|
325
547
|
string,
|
|
@@ -331,7 +553,12 @@ export function createAbloHttpClient<S extends SchemaRecord>(
|
|
|
331
553
|
): HttpModelClient<Record<string, unknown>, Record<string, unknown>> => {
|
|
332
554
|
const cached = models.get(name);
|
|
333
555
|
if (cached) return cached;
|
|
334
|
-
const created = createHttpModelClient(
|
|
556
|
+
const created = createHttpModelClient(
|
|
557
|
+
transport.model(name),
|
|
558
|
+
name,
|
|
559
|
+
clientIdentity,
|
|
560
|
+
readSetContext,
|
|
561
|
+
);
|
|
335
562
|
models.set(name, created);
|
|
336
563
|
return created;
|
|
337
564
|
};
|
|
@@ -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
|
}
|