@abloatai/ablo 0.29.3 → 0.30.1
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/CHANGELOG.md +31 -0
- package/README.md +3 -2
- package/dist/BaseSyncedStore.js +3 -1
- package/dist/Database.js +29 -2
- package/dist/SyncClient.d.ts +5 -3
- package/dist/SyncClient.js +8 -4
- package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
- package/dist/ai-sdk/coordinatedTool.js +0 -1
- package/dist/ai-sdk/coordinationContext.js +1 -5
- package/dist/cli.cjs +377 -84
- package/dist/client/Ablo.d.ts +1 -1
- package/dist/client/Ablo.js +24 -36
- package/dist/client/auth.d.ts +8 -18
- package/dist/client/auth.js +16 -96
- package/dist/client/createModelProxy.d.ts +16 -11
- package/dist/client/createModelProxy.js +162 -20
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/httpTransport.js +263 -89
- package/dist/client/options.d.ts +0 -15
- package/dist/client/resourceTypes.d.ts +13 -26
- package/dist/client/schemaConfig.js +5 -0
- package/dist/client/writeOptionsSchema.d.ts +1 -0
- package/dist/client/writeOptionsSchema.js +11 -2
- package/dist/client/wsMutationExecutor.d.ts +3 -5
- package/dist/coordination/schema.d.ts +17 -13
- package/dist/coordination/schema.js +36 -10
- package/dist/errorCodes.d.ts +9 -4
- package/dist/errorCodes.js +10 -5
- package/dist/errors.js +4 -9
- package/dist/index.d.ts +4 -0
- package/dist/interfaces/index.d.ts +31 -19
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/schema.d.ts +13 -0
- package/dist/schema/select.js +7 -0
- package/dist/schema/syncDeltaRow.d.ts +31 -13
- package/dist/schema/syncDeltaRow.js +33 -15
- package/dist/schema/tenancy.d.ts +52 -16
- package/dist/schema/tenancy.js +86 -25
- package/dist/server/commit.d.ts +8 -24
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/readConfig.d.ts +10 -0
- package/dist/source/adapter.d.ts +26 -15
- package/dist/source/adapter.js +5 -3
- package/dist/source/adapters/drizzle.js +35 -9
- package/dist/source/adapters/kysely.d.ts +33 -61
- package/dist/source/adapters/kysely.js +170 -177
- package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
- package/dist/source/adapters/kyselyMutationCore.js +125 -0
- package/dist/source/adapters/memory.d.ts +1 -1
- package/dist/source/adapters/memory.js +24 -10
- package/dist/source/adapters/prisma.js +35 -8
- package/dist/source/conformance.d.ts +26 -28
- package/dist/source/conformance.js +142 -69
- package/dist/source/contract.d.ts +46 -4
- package/dist/source/contract.js +66 -5
- package/dist/source/factory.d.ts +3 -3
- package/dist/source/factory.js +24 -3
- package/dist/source/idempotency.d.ts +46 -0
- package/dist/source/idempotency.js +129 -0
- package/dist/source/index.d.ts +6 -4
- package/dist/source/index.js +5 -3
- package/dist/source/migrations.d.ts +9 -5
- package/dist/source/migrations.js +39 -6
- package/dist/source/types.d.ts +88 -14
- package/dist/source/types.js +16 -0
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +0 -1
- package/dist/sync/BootstrapFetcher.js +7 -0
- package/dist/sync/SyncWebSocket.d.ts +5 -27
- package/dist/sync/awaitClaimGrant.d.ts +6 -0
- package/dist/sync/awaitClaimGrant.js +12 -2
- package/dist/sync/commitFrames.d.ts +2 -4
- package/dist/sync/commitFrames.js +1 -0
- package/dist/sync/createClaimStream.d.ts +1 -1
- package/dist/sync/createClaimStream.js +13 -15
- package/dist/sync/deltaPipeline.d.ts +1 -1
- package/dist/sync/deltaPipeline.js +1 -1
- package/dist/sync/participants.d.ts +2 -3
- package/dist/sync/participants.js +2 -2
- package/dist/sync/wsFrameHandlers.js +35 -15
- package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
- package/dist/testing/mocks/MockMutationExecutor.js +53 -1
- package/dist/transactions/TransactionQueue.d.ts +61 -9
- package/dist/transactions/TransactionQueue.js +502 -114
- package/dist/transactions/commitEnvelope.d.ts +4 -0
- package/dist/transactions/commitEnvelope.js +25 -0
- package/dist/transactions/commitPayload.d.ts +23 -5
- package/dist/transactions/commitPayload.js +3 -0
- package/dist/transactions/deltaConfirmation.d.ts +2 -1
- package/dist/transactions/deltaConfirmation.js +22 -4
- package/dist/transactions/durableWriteStore.d.ts +8 -1
- package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
- package/dist/transactions/httpCommitEnvelope.js +30 -0
- package/dist/transactions/replayValidation.d.ts +4 -0
- package/dist/transactions/replayValidation.js +6 -0
- package/dist/types/streams.d.ts +29 -17
- package/dist/wire/commit.d.ts +671 -0
- package/dist/wire/commit.js +300 -0
- package/dist/wire/delta.d.ts +4 -0
- package/dist/wire/delta.js +9 -0
- package/dist/wire/frames.d.ts +17 -45
- package/dist/wire/index.d.ts +2 -0
- package/dist/wire/index.js +4 -0
- package/docs/agent-messaging.md +4 -5
- package/docs/agents.md +1 -2
- package/docs/api-keys.md +1 -2
- package/docs/client-behavior.md +4 -6
- package/docs/coordination.md +64 -9
- package/docs/data-sources.md +4 -7
- package/docs/integration-guide.md +6 -8
- package/docs/migration.md +0 -3
- package/docs/quickstart.md +7 -8
- package/docs/schema-contract.md +7 -8
- package/package.json +4 -4
- package/dist/client/registerDataSource.d.ts +0 -19
- package/dist/client/registerDataSource.js +0 -66
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* {@link HttpClaimApi} derivation. This module holds only types and has no runtime
|
|
5
5
|
* imports.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { ReadDependency } from '../coordination/schema.js';
|
|
8
|
+
import type { ClientCommitReceipt, CommitStatus } from '../wire/commit.js';
|
|
8
9
|
import type { ModelTarget, ModelClaim } from '../coordination/schema.js';
|
|
9
10
|
export type { ModelTarget, ModelClaim };
|
|
10
11
|
import type { SchemaRecord } from '../schema/schema.js';
|
|
@@ -19,9 +20,9 @@ import type { ClaimOptions, ClaimParams, ClaimReadApi, AwaitedClaimMethod } from
|
|
|
19
20
|
* `create({ data })` / `update({ id, data })` / `delete({ id })` — writes
|
|
20
21
|
* `claim({ id })` — a durable claim handle for coordinated writes
|
|
21
22
|
*/
|
|
22
|
-
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, ModelOperations, } from './createModelProxy.js';
|
|
23
|
+
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, ModelOperations, } from './createModelProxy.js';
|
|
23
24
|
export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
|
|
24
|
-
export type CommitWait =
|
|
25
|
+
export type CommitWait = CommitStatus;
|
|
25
26
|
export type IfClaimedPolicy = 'return' | 'fail';
|
|
26
27
|
export interface ClaimedOptions {
|
|
27
28
|
/**
|
|
@@ -37,9 +38,9 @@ export interface ModelReadOptions extends ClaimedOptions {
|
|
|
37
38
|
}
|
|
38
39
|
export interface ClaimCreateOptions {
|
|
39
40
|
readonly target: ModelTarget;
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
readonly
|
|
41
|
+
/** Peer-visible description of the work — the same field on every claim
|
|
42
|
+
* surface. Defaults to `'editing'` when omitted. */
|
|
43
|
+
readonly description?: string;
|
|
43
44
|
readonly ttl?: Duration;
|
|
44
45
|
/**
|
|
45
46
|
* Join the server's fair FIFO queue when the target is already claimed,
|
|
@@ -67,6 +68,8 @@ export interface CommitOperationInput {
|
|
|
67
68
|
readonly transactionId?: string | null;
|
|
68
69
|
readonly readAt?: number | null;
|
|
69
70
|
readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
71
|
+
/** Fencing token (Option B) from the batch's claim handle; server-validated. */
|
|
72
|
+
readonly fenceToken?: number | null;
|
|
70
73
|
}
|
|
71
74
|
export interface CommitCreateOptions {
|
|
72
75
|
readonly idempotencyKey?: string | null;
|
|
@@ -94,26 +97,8 @@ export interface CommitCreateOptions {
|
|
|
94
97
|
*/
|
|
95
98
|
readonly reads?: readonly ReadDependency[] | null;
|
|
96
99
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
readonly status: CommitWait;
|
|
100
|
-
readonly lastSyncId?: number;
|
|
101
|
-
/**
|
|
102
|
-
* Stale-context notifications: present only when this commit guarded a write with
|
|
103
|
-
* `onStale: 'notify'` and the premise moved concurrently. Each carries the
|
|
104
|
-
* conflicting field's current value, handed back as data rather than raising an
|
|
105
|
-
* `AbloStaleContextError`, so the caller — an agent or a human — decides how to
|
|
106
|
-
* resolve it.
|
|
107
|
-
*/
|
|
108
|
-
readonly notifications?: readonly StaleNotification[];
|
|
109
|
-
/**
|
|
110
|
-
* Ids of update or delete targets in this commit that matched no rows, because
|
|
111
|
-
* the row does not exist or is outside the caller's organization. Present and
|
|
112
|
-
* non-empty only when a write missed. The typed resource wrappers turn this into
|
|
113
|
-
* an `AbloNotFoundError`; a raw `commits.create` caller can inspect it directly.
|
|
114
|
-
*/
|
|
115
|
-
readonly missingIds?: readonly string[];
|
|
116
|
-
}
|
|
100
|
+
/** Public projection inferred from the canonical runtime schema. */
|
|
101
|
+
export type CommitReceipt = ClientCommitReceipt;
|
|
117
102
|
export interface CommitResource {
|
|
118
103
|
create(options: CommitCreateOptions): Promise<CommitReceipt>;
|
|
119
104
|
}
|
|
@@ -131,6 +116,8 @@ export interface ModelMutationOptions extends ClaimedOptions {
|
|
|
131
116
|
readonly onStale?: 'reject' | 'overwrite' | 'notify' | null;
|
|
132
117
|
readonly wait?: CommitWait;
|
|
133
118
|
readonly claim?: Claim | ClaimOptions | null;
|
|
119
|
+
/** Fencing token (Option B) from the claim; server-validated at commit. */
|
|
120
|
+
readonly fenceToken?: number | null;
|
|
134
121
|
}
|
|
135
122
|
/**
|
|
136
123
|
* The stateless HTTP claim surface. Most code puts a `claim` directly on the write
|
|
@@ -172,5 +172,10 @@ export function deriveConfigFromSchema(schema) {
|
|
|
172
172
|
// schema the server currently has active. The server and the `ablo push`
|
|
173
173
|
// command compute this same hash.
|
|
174
174
|
expectedSchemaHash: schemaHash(schema),
|
|
175
|
+
// For a projection (`selectModels`/`omitModels`), also carry the full source
|
|
176
|
+
// schema's hash. The drift check accepts a server match on either hash, so a
|
|
177
|
+
// subset client stays quiet against a server running its full source schema.
|
|
178
|
+
// Undefined for a directly-authored schema — plain equality applies there.
|
|
179
|
+
expectedSourceSchemaHash: schema.sourceSchemaHash,
|
|
175
180
|
};
|
|
176
181
|
}
|
|
@@ -34,6 +34,7 @@ export declare const writeOptionsSchema: z.ZodObject<{
|
|
|
34
34
|
overwrite: "overwrite";
|
|
35
35
|
notify: "notify";
|
|
36
36
|
}>>>;
|
|
37
|
+
fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
37
38
|
claim: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
38
39
|
id: z.ZodString;
|
|
39
40
|
}, z.core.$loose>]>>>;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { z } from 'zod';
|
|
19
19
|
import { AbloValidationError } from '../errors.js';
|
|
20
|
+
import { commitStatusSchema } from '../wire/commit.js';
|
|
20
21
|
export const onStaleModeSchema = z.enum(['reject', 'overwrite', 'notify']);
|
|
21
22
|
export const writeOptionsSchema = z.object({
|
|
22
23
|
/** Idempotency key the server records in `mutation_log` to make retries
|
|
@@ -25,11 +26,16 @@ export const writeOptionsSchema = z.object({
|
|
|
25
26
|
/** Human-readable audit tag, persisted to `mutation_log.label`. */
|
|
26
27
|
label: z.string().max(255).optional(),
|
|
27
28
|
/** Resolve when queued locally (default) or once the server confirms. */
|
|
28
|
-
wait:
|
|
29
|
+
wait: commitStatusSchema.optional(),
|
|
29
30
|
/** Stale guard: the sync watermark the caller's reasoning was based on. */
|
|
30
31
|
readAt: z.number().int().nonnegative().nullish(),
|
|
31
32
|
/** What the server does when the target moved past `readAt`. */
|
|
32
33
|
onStale: onStaleModeSchema.nullish(),
|
|
34
|
+
/** The held claim's fencing token (Option B), sourced from the claim handle
|
|
35
|
+
* rather than set by hand; the server validates it against the entity's
|
|
36
|
+
* high-water. Kept in the schema so it stays aligned with `MutationOptions`
|
|
37
|
+
* (the drift guard below) and a malformed token is rejected at the boundary. */
|
|
38
|
+
fenceToken: z.number().nullish(),
|
|
33
39
|
/** The claim this write belongs to — either a claim id, or a live claim
|
|
34
40
|
* handle whose `release`/`revoke` functions are preserved untouched. */
|
|
35
41
|
claim: z.union([z.string(), z.looseObject({ id: z.string() })]).nullish(),
|
|
@@ -56,5 +62,8 @@ export function assertWriteOptions(value, context) {
|
|
|
56
62
|
...(path ? { param: path } : {}),
|
|
57
63
|
});
|
|
58
64
|
}
|
|
59
|
-
const _writeOptionsContractInSync = [
|
|
65
|
+
const _writeOptionsContractInSync = [
|
|
66
|
+
true,
|
|
67
|
+
true,
|
|
68
|
+
];
|
|
60
69
|
void _writeOptionsContractInSync;
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* commit time because it does not exist yet when the executor is created. A
|
|
5
5
|
* client wires this up automatically unless you supply your own executor.
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { ReadDependency } from '../coordination/schema.js';
|
|
8
8
|
import type { MutationExecutor, MutationOperation } from '../interfaces/index.js';
|
|
9
|
+
import type { CommitAck } from '../sync/commitFrames.js';
|
|
9
10
|
/**
|
|
10
11
|
* Creates the default mutation executor, which sends each change as a `commit`
|
|
11
12
|
* frame over the sync engine's own WebSocket. The engine owns its socket, so
|
|
@@ -22,8 +23,5 @@ import type { MutationExecutor, MutationOperation } from '../interfaces/index.js
|
|
|
22
23
|
* generation below exists only for direct, one-shot executor consumers.
|
|
23
24
|
*/
|
|
24
25
|
export declare function createDefaultMutationExecutor(getWs: () => {
|
|
25
|
-
sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, causedByTaskId?: string | null, reads?: readonly ReadDependency[] | null) => Promise<
|
|
26
|
-
lastSyncId: number;
|
|
27
|
-
notifications?: StaleNotification[];
|
|
28
|
-
}>;
|
|
26
|
+
sendCommit?: (operations: readonly MutationOperation[], clientTxId: string, timeoutMs?: number, causedByTaskId?: string | null, reads?: readonly ReadDependency[] | null) => Promise<CommitAck>;
|
|
29
27
|
} | null): MutationExecutor;
|
|
@@ -196,11 +196,11 @@ export declare const claimStatusSchema: z.ZodEnum<{
|
|
|
196
196
|
export type ClaimStatus = z.infer<typeof claimStatusSchema>;
|
|
197
197
|
export declare const wireClaimSummarySchema: z.ZodObject<{
|
|
198
198
|
field: z.ZodOptional<z.ZodString>;
|
|
199
|
+
description: z.ZodOptional<z.ZodString>;
|
|
199
200
|
entityType: z.ZodString;
|
|
200
201
|
entityId: z.ZodString;
|
|
201
202
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
202
203
|
claimId: z.ZodString;
|
|
203
|
-
reason: z.ZodString;
|
|
204
204
|
declaredAt: z.ZodNumber;
|
|
205
205
|
expiresAt: z.ZodNumber;
|
|
206
206
|
}, z.core.$strip>;
|
|
@@ -214,11 +214,11 @@ export declare const claimErrorSchema: z.ZodObject<{
|
|
|
214
214
|
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
215
215
|
heldByClaim: z.ZodOptional<z.ZodObject<{
|
|
216
216
|
field: z.ZodOptional<z.ZodString>;
|
|
217
|
+
description: z.ZodOptional<z.ZodString>;
|
|
217
218
|
entityType: z.ZodString;
|
|
218
219
|
entityId: z.ZodString;
|
|
219
220
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
220
221
|
claimId: z.ZodString;
|
|
221
|
-
reason: z.ZodString;
|
|
222
222
|
declaredAt: z.ZodNumber;
|
|
223
223
|
expiresAt: z.ZodNumber;
|
|
224
224
|
}, z.core.$strip>>;
|
|
@@ -228,7 +228,7 @@ export type ClaimError = z.infer<typeof claimErrorSchema>;
|
|
|
228
228
|
/**
|
|
229
229
|
* A declared, pending-mutation claim — the unit broadcast inside a presence
|
|
230
230
|
* frame's `activeClaims`. The client supplies the descriptive `targetRef`
|
|
231
|
-
* fields,
|
|
231
|
+
* fields, a `description` of the work, and a chosen `claimId`; the server stamps
|
|
232
232
|
* `declaredAt` and `expiresAt` and may set `status` and `error`. Those last
|
|
233
233
|
* two are optional, so one shape serves both the server, which sets them, and
|
|
234
234
|
* the leaner SDK view, which reads a claim without them.
|
|
@@ -246,7 +246,7 @@ export declare const wireClaimSchema: z.ZodObject<{
|
|
|
246
246
|
field: z.ZodOptional<z.ZodString>;
|
|
247
247
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
248
248
|
claimId: z.ZodString;
|
|
249
|
-
|
|
249
|
+
description: z.ZodOptional<z.ZodString>;
|
|
250
250
|
declaredAt: z.ZodNumber;
|
|
251
251
|
expiresAt: z.ZodNumber;
|
|
252
252
|
status: z.ZodOptional<z.ZodEnum<{
|
|
@@ -255,6 +255,7 @@ export declare const wireClaimSchema: z.ZodObject<{
|
|
|
255
255
|
expired: "expired";
|
|
256
256
|
canceled: "canceled";
|
|
257
257
|
}>>;
|
|
258
|
+
fenceToken: z.ZodOptional<z.ZodNumber>;
|
|
258
259
|
error: z.ZodOptional<z.ZodObject<{
|
|
259
260
|
code: z.ZodString;
|
|
260
261
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -263,11 +264,11 @@ export declare const wireClaimSchema: z.ZodObject<{
|
|
|
263
264
|
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
264
265
|
heldByClaim: z.ZodOptional<z.ZodObject<{
|
|
265
266
|
field: z.ZodOptional<z.ZodString>;
|
|
267
|
+
description: z.ZodOptional<z.ZodString>;
|
|
266
268
|
entityType: z.ZodString;
|
|
267
269
|
entityId: z.ZodString;
|
|
268
270
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
269
271
|
claimId: z.ZodString;
|
|
270
|
-
reason: z.ZodString;
|
|
271
272
|
declaredAt: z.ZodNumber;
|
|
272
273
|
expiresAt: z.ZodNumber;
|
|
273
274
|
}, z.core.$strip>>;
|
|
@@ -296,11 +297,11 @@ export declare const claimRejectionSchema: z.ZodObject<{
|
|
|
296
297
|
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
297
298
|
heldByClaim: z.ZodOptional<z.ZodObject<{
|
|
298
299
|
field: z.ZodOptional<z.ZodString>;
|
|
300
|
+
description: z.ZodOptional<z.ZodString>;
|
|
299
301
|
entityType: z.ZodString;
|
|
300
302
|
entityId: z.ZodString;
|
|
301
303
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
302
304
|
claimId: z.ZodString;
|
|
303
|
-
reason: z.ZodString;
|
|
304
305
|
declaredAt: z.ZodNumber;
|
|
305
306
|
expiresAt: z.ZodNumber;
|
|
306
307
|
}, z.core.$strip>>;
|
|
@@ -373,7 +374,6 @@ export declare const modelClaimSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
373
374
|
agent: "agent";
|
|
374
375
|
system: "system";
|
|
375
376
|
}>>;
|
|
376
|
-
reason: z.ZodString;
|
|
377
377
|
description: z.ZodOptional<z.ZodString>;
|
|
378
378
|
field: z.ZodOptional<z.ZodString>;
|
|
379
379
|
status: z.ZodOptional<z.ZodEnum<{
|
|
@@ -382,6 +382,7 @@ export declare const modelClaimSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
382
382
|
}>>;
|
|
383
383
|
position: z.ZodOptional<z.ZodNumber>;
|
|
384
384
|
expiresAt: z.ZodNumber;
|
|
385
|
+
fenceToken: z.ZodOptional<z.ZodNumber>;
|
|
385
386
|
target: z.ZodReadonly<z.ZodObject<{
|
|
386
387
|
model: z.ZodString;
|
|
387
388
|
id: z.ZodString;
|
|
@@ -399,9 +400,10 @@ export declare const modelClaimSchema: z.ZodReadonly<z.ZodObject<{
|
|
|
399
400
|
export type ModelClaim = z.infer<typeof modelClaimSchema>;
|
|
400
401
|
/**
|
|
401
402
|
* The `claim_begin` payload a client sends. It carries the descriptive target
|
|
402
|
-
* and
|
|
403
|
-
* server stamps the lifecycle and timestamp fields, so they
|
|
404
|
-
* this inbound shape — this is exactly what the server validates
|
|
403
|
+
* and a `description` of the work, an optional duration hint, and the opt-in
|
|
404
|
+
* fair-queue flag. The server stamps the lifecycle and timestamp fields, so they
|
|
405
|
+
* are not part of this inbound shape — this is exactly what the server validates
|
|
406
|
+
* on ingest.
|
|
405
407
|
*/
|
|
406
408
|
export declare const claimBeginPayloadSchema: z.ZodObject<{
|
|
407
409
|
entityType: z.ZodString;
|
|
@@ -416,7 +418,7 @@ export declare const claimBeginPayloadSchema: z.ZodObject<{
|
|
|
416
418
|
field: z.ZodOptional<z.ZodString>;
|
|
417
419
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
418
420
|
claimId: z.ZodString;
|
|
419
|
-
|
|
421
|
+
description: z.ZodOptional<z.ZodString>;
|
|
420
422
|
estimatedMs: z.ZodOptional<z.ZodNumber>;
|
|
421
423
|
queue: z.ZodOptional<z.ZodBoolean>;
|
|
422
424
|
}, z.core.$strip>;
|
|
@@ -579,6 +581,7 @@ export declare const commitOperationSchema: z.ZodObject<{
|
|
|
579
581
|
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
580
582
|
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
581
583
|
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
584
|
+
fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
582
585
|
}, z.core.$strip>;
|
|
583
586
|
export type CommitOperation = z.infer<typeof commitOperationSchema>;
|
|
584
587
|
/**
|
|
@@ -663,7 +666,7 @@ export declare const presenceUpdateFrameSchema: z.ZodObject<{
|
|
|
663
666
|
field: z.ZodOptional<z.ZodString>;
|
|
664
667
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
665
668
|
claimId: z.ZodString;
|
|
666
|
-
|
|
669
|
+
description: z.ZodOptional<z.ZodString>;
|
|
667
670
|
declaredAt: z.ZodNumber;
|
|
668
671
|
expiresAt: z.ZodNumber;
|
|
669
672
|
status: z.ZodOptional<z.ZodEnum<{
|
|
@@ -672,6 +675,7 @@ export declare const presenceUpdateFrameSchema: z.ZodObject<{
|
|
|
672
675
|
expired: "expired";
|
|
673
676
|
canceled: "canceled";
|
|
674
677
|
}>>;
|
|
678
|
+
fenceToken: z.ZodOptional<z.ZodNumber>;
|
|
675
679
|
error: z.ZodOptional<z.ZodObject<{
|
|
676
680
|
code: z.ZodString;
|
|
677
681
|
message: z.ZodOptional<z.ZodString>;
|
|
@@ -680,11 +684,11 @@ export declare const presenceUpdateFrameSchema: z.ZodObject<{
|
|
|
680
684
|
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
681
685
|
heldByClaim: z.ZodOptional<z.ZodObject<{
|
|
682
686
|
field: z.ZodOptional<z.ZodString>;
|
|
687
|
+
description: z.ZodOptional<z.ZodString>;
|
|
683
688
|
entityType: z.ZodString;
|
|
684
689
|
entityId: z.ZodString;
|
|
685
690
|
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
686
691
|
claimId: z.ZodString;
|
|
687
|
-
reason: z.ZodString;
|
|
688
692
|
declaredAt: z.ZodNumber;
|
|
689
693
|
expiresAt: z.ZodNumber;
|
|
690
694
|
}, z.core.$strip>>;
|
|
@@ -200,17 +200,30 @@ export const claimStatusSchema = z.enum([
|
|
|
200
200
|
]);
|
|
201
201
|
const wireClaimBaseSchema = targetRefSchema.extend({
|
|
202
202
|
claimId: z.string(),
|
|
203
|
-
/**
|
|
204
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Peer-visible description of the work being done (`'rewriting the risk
|
|
205
|
+
* section to match Q3'`). The server stamps a default when a frame carries
|
|
206
|
+
* none.
|
|
207
|
+
*/
|
|
208
|
+
description: z.string().optional(),
|
|
205
209
|
/** Server-stamped declaration time (epoch ms). */
|
|
206
210
|
declaredAt: z.number(),
|
|
207
211
|
/** Server-computed TTL deadline (epoch ms). Readers treat as advisory. */
|
|
208
212
|
expiresAt: z.number(),
|
|
209
213
|
status: claimStatusSchema.optional(),
|
|
214
|
+
/**
|
|
215
|
+
* The monotonic fencing token the server minted for this grant (Option B).
|
|
216
|
+
* Strictly increasing per entity across successive grants, so a write that
|
|
217
|
+
* carries it can be rejected at commit if a later holder already advanced the
|
|
218
|
+
* entity's high-water. Server-stamped on grant and never client-supplied;
|
|
219
|
+
* optional so every existing frame stays valid and a token-less write is
|
|
220
|
+
* simply not fence-checked.
|
|
221
|
+
*/
|
|
222
|
+
fenceToken: z.number().optional(),
|
|
210
223
|
});
|
|
211
224
|
export const wireClaimSummarySchema = wireClaimBaseSchema.pick({
|
|
212
225
|
claimId: true,
|
|
213
|
-
|
|
226
|
+
description: true,
|
|
214
227
|
declaredAt: true,
|
|
215
228
|
expiresAt: true,
|
|
216
229
|
entityType: true,
|
|
@@ -234,7 +247,7 @@ export const claimErrorSchema = z.object({
|
|
|
234
247
|
/**
|
|
235
248
|
* A declared, pending-mutation claim — the unit broadcast inside a presence
|
|
236
249
|
* frame's `activeClaims`. The client supplies the descriptive `targetRef`
|
|
237
|
-
* fields,
|
|
250
|
+
* fields, a `description` of the work, and a chosen `claimId`; the server stamps
|
|
238
251
|
* `declaredAt` and `expiresAt` and may set `status` and `error`. Those last
|
|
239
252
|
* two are optional, so one shape serves both the server, which sets them, and
|
|
240
253
|
* the leaner SDK view, which reads a claim without them.
|
|
@@ -295,25 +308,30 @@ export const modelClaimSchema = z
|
|
|
295
308
|
id: z.string(),
|
|
296
309
|
actor: z.string(),
|
|
297
310
|
participantKind: wireParticipantKindSchema,
|
|
298
|
-
/**
|
|
299
|
-
|
|
311
|
+
/** Peer-visible description of the work (`'rewriting the risk section'`).
|
|
312
|
+
* Optional: a claim may be declared without one. */
|
|
300
313
|
description: z.string().optional(),
|
|
301
314
|
field: z.string().optional(),
|
|
302
315
|
status: z.enum(['active', 'queued']).optional(),
|
|
303
316
|
position: z.number().optional(),
|
|
304
317
|
expiresAt: z.number(),
|
|
318
|
+
/** The grant's fencing token (Option B), present on a claim you hold. */
|
|
319
|
+
fenceToken: z.number().optional(),
|
|
305
320
|
target: modelTargetSchema,
|
|
306
321
|
})
|
|
307
322
|
.readonly();
|
|
308
323
|
/**
|
|
309
324
|
* The `claim_begin` payload a client sends. It carries the descriptive target
|
|
310
|
-
* and
|
|
311
|
-
* server stamps the lifecycle and timestamp fields, so they
|
|
312
|
-
* this inbound shape — this is exactly what the server validates
|
|
325
|
+
* and a `description` of the work, an optional duration hint, and the opt-in
|
|
326
|
+
* fair-queue flag. The server stamps the lifecycle and timestamp fields, so they
|
|
327
|
+
* are not part of this inbound shape — this is exactly what the server validates
|
|
328
|
+
* on ingest.
|
|
313
329
|
*/
|
|
314
330
|
export const claimBeginPayloadSchema = targetRefSchema.extend({
|
|
315
331
|
claimId: z.string(),
|
|
316
|
-
|
|
332
|
+
/** Peer-visible description of the work. The server stamps `'editing'` when a
|
|
333
|
+
* frame carries none. */
|
|
334
|
+
description: z.string().optional(),
|
|
317
335
|
/** Hint for `expiresAt`; the server caps it. */
|
|
318
336
|
estimatedMs: z.number().optional(),
|
|
319
337
|
/**
|
|
@@ -481,6 +499,14 @@ export const commitOperationSchema = writeGuardSchema.extend({
|
|
|
481
499
|
input: z.record(z.string(), z.unknown()).nullish(),
|
|
482
500
|
/** Per-op client tx id, echoed on the broadcast delta. */
|
|
483
501
|
transactionId: z.string().nullish(),
|
|
502
|
+
/**
|
|
503
|
+
* The fencing token from the held claim this write belongs to (Option B).
|
|
504
|
+
* Present only on a write issued under a claim that was granted one; the
|
|
505
|
+
* server checks it against the entity's persisted high-water and rejects a
|
|
506
|
+
* stale token. Absent (nullish) on every unclaimed write — those are governed
|
|
507
|
+
* by version-CAS and the Option A blind-write guard, unchanged.
|
|
508
|
+
*/
|
|
509
|
+
fenceToken: z.number().nullish(),
|
|
484
510
|
});
|
|
485
511
|
// ─────────────────────────────────────────────────────────────────────────
|
|
486
512
|
// Layer 1 — presence (observation only; it never enforces)
|
package/dist/errorCodes.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { z } from 'zod';
|
|
|
37
37
|
* error documentation and returned on the `Ablo-Version` response header, so a
|
|
38
38
|
* consumer can detect when its expected contract has drifted from the server's.
|
|
39
39
|
*/
|
|
40
|
-
export declare const ERROR_CONTRACT_VERSION = "2026-07-
|
|
40
|
+
export declare const ERROR_CONTRACT_VERSION = "2026-07-15";
|
|
41
41
|
/** A coarse grouping of error codes, used to organize metrics and documentation. */
|
|
42
42
|
export type ErrorCategory = 'auth' | 'permission' | 'capability' | 'claim' | 'conflict' | 'validation' | 'not_found' | 'tenant' | 'schema' | 'bootstrap' | 'transport' | 'rate_limit' | 'server' | 'client';
|
|
43
43
|
/**
|
|
@@ -138,10 +138,7 @@ export declare const ERROR_CODES: {
|
|
|
138
138
|
readonly jwt_org_membership_denied: ErrorCodeSpec;
|
|
139
139
|
readonly file_upload_auth_required: ErrorCodeSpec;
|
|
140
140
|
readonly browser_apikey_blocked: ErrorCodeSpec;
|
|
141
|
-
readonly browser_database_url_blocked: ErrorCodeSpec;
|
|
142
|
-
readonly datasource_registration_failed: ErrorCodeSpec;
|
|
143
141
|
readonly datasource_connection_unsupported: ErrorCodeSpec;
|
|
144
|
-
readonly datasource_direct_deprecated: ErrorCodeSpec;
|
|
145
142
|
readonly capability_scope_denied: ErrorCodeSpec;
|
|
146
143
|
readonly issuer_register_forbidden: ErrorCodeSpec;
|
|
147
144
|
readonly capability_invalid: ErrorCodeSpec;
|
|
@@ -157,6 +154,7 @@ export declare const ERROR_CODES: {
|
|
|
157
154
|
readonly byo_host_not_allowed: ErrorCodeSpec;
|
|
158
155
|
readonly claim_conflict: ErrorCodeSpec;
|
|
159
156
|
readonly claim_lost: ErrorCodeSpec;
|
|
157
|
+
readonly fence_token_stale: ErrorCodeSpec;
|
|
160
158
|
readonly entity_claimed: ErrorCodeSpec;
|
|
161
159
|
readonly malformed_claim: ErrorCodeSpec;
|
|
162
160
|
readonly malformed_subscription: ErrorCodeSpec;
|
|
@@ -168,6 +166,8 @@ export declare const ERROR_CODES: {
|
|
|
168
166
|
readonly contention_exhausted: ErrorCodeSpec;
|
|
169
167
|
readonly update_aborted: ErrorCodeSpec;
|
|
170
168
|
readonly idempotency_conflict: ErrorCodeSpec;
|
|
169
|
+
readonly idempotency_key_expired: ErrorCodeSpec;
|
|
170
|
+
readonly source_transport_pinned: ErrorCodeSpec;
|
|
171
171
|
readonly idempotency_key_too_long: ErrorCodeSpec;
|
|
172
172
|
readonly write_options_invalid: ErrorCodeSpec;
|
|
173
173
|
readonly source_operation_id_required: ErrorCodeSpec;
|
|
@@ -236,9 +236,12 @@ export declare const ERROR_CODES: {
|
|
|
236
236
|
readonly exchange_malformed_response: ErrorCodeSpec;
|
|
237
237
|
readonly exchange_network_error: ErrorCodeSpec;
|
|
238
238
|
readonly source_network_error: ErrorCodeSpec;
|
|
239
|
+
readonly source_unreachable: ErrorCodeSpec;
|
|
240
|
+
readonly data_source_blocked: ErrorCodeSpec;
|
|
239
241
|
readonly identity_network_error: ErrorCodeSpec;
|
|
240
242
|
readonly commit_no_result: ErrorCodeSpec;
|
|
241
243
|
readonly commit_failed: ErrorCodeSpec;
|
|
244
|
+
readonly replication_lag_timeout: ErrorCodeSpec;
|
|
242
245
|
readonly commit_offline_grace_expired: ErrorCodeSpec;
|
|
243
246
|
readonly queue_too_deep: ErrorCodeSpec;
|
|
244
247
|
readonly flush_timeout: ErrorCodeSpec;
|
|
@@ -308,6 +311,8 @@ export declare const ERROR_CODES: {
|
|
|
308
311
|
readonly invalid_id: ErrorCodeSpec;
|
|
309
312
|
readonly unknown_model: ErrorCodeSpec;
|
|
310
313
|
readonly model_not_tenant_scoped: ErrorCodeSpec;
|
|
314
|
+
readonly source_tenancy_not_enforced: ErrorCodeSpec;
|
|
315
|
+
readonly model_not_provisioned: ErrorCodeSpec;
|
|
311
316
|
readonly schema_table_invalid: ErrorCodeSpec;
|
|
312
317
|
readonly schema_scope_invalid: ErrorCodeSpec;
|
|
313
318
|
readonly entity_fetch_failed: ErrorCodeSpec;
|
package/dist/errorCodes.js
CHANGED
|
@@ -37,7 +37,7 @@ import { z } from 'zod';
|
|
|
37
37
|
* error documentation and returned on the `Ablo-Version` response header, so a
|
|
38
38
|
* consumer can detect when its expected contract has drifted from the server's.
|
|
39
39
|
*/
|
|
40
|
-
export const ERROR_CONTRACT_VERSION = '2026-07-
|
|
40
|
+
export const ERROR_CONTRACT_VERSION = '2026-07-15';
|
|
41
41
|
/**
|
|
42
42
|
* A closed classification of how a failure can be recovered from — a level above
|
|
43
43
|
* the raw {@link ErrorCode}. Where a code says what went wrong, a recovery class
|
|
@@ -125,15 +125,12 @@ export const ERROR_CODES = {
|
|
|
125
125
|
jwt_org_membership_denied: wire('auth', 403, false, "The bearer JWT's subject is not an active member of the organization in its `org_id` claim (removed, suspended, or the claim does not match a membership)."),
|
|
126
126
|
file_upload_auth_required: wire('auth', 401, false, 'File uploads require an authenticated session. Sign in and retry.'),
|
|
127
127
|
browser_apikey_blocked: client('auth', 'A raw API key was used from a browser, where anyone can read it. Keep secret keys server-side and hand the browser a short-lived ephemeral key instead.'),
|
|
128
|
-
browser_database_url_blocked: client('auth', 'A database connection string was used from a browser context. It carries database credentials, so it must stay server-side.'),
|
|
129
|
-
datasource_registration_failed: client('auth', 'The provided `databaseUrl` could not be registered as a data source. Check that the connection string is valid and the database is reachable.'),
|
|
130
128
|
datasource_connection_unsupported: wire('validation', 400, false, 'This deployment does not accept direct connection-string data sources. Register a signed Data Source endpoint instead.'),
|
|
131
|
-
datasource_direct_deprecated: wire('validation', 410, false, 'The direct (connection string) datasource is deprecated. Register a signed Data Source endpoint instead — your app owns the write and your credentials never leave it.'),
|
|
132
129
|
// ── permission / capability (403) ──────────────────────────────────
|
|
133
130
|
capability_scope_denied: wire('capability', 403, false, 'This action falls outside the scope granted to the connection, so it was denied.'),
|
|
134
131
|
issuer_register_forbidden: wire('permission', 403, false, 'Registering a trusted issuer requires a secret (`sk_`) API key. The key presented is not a secret key.'),
|
|
135
132
|
capability_invalid: wire('capability', 403, false, 'This capability cannot be used — it is unknown, revoked, or expired. Request a fresh grant.'),
|
|
136
|
-
test_database_not_registered: wire('permission', 403, false, 'Test mode requires a registered dev database for this org — run `npx ablo init
|
|
133
|
+
test_database_not_registered: wire('permission', 403, false, 'Test mode requires a registered dev database for this org — run `npx ablo init` to register one for your test key.'),
|
|
137
134
|
tenant_routing_failed: wire('server', 500, true, "The org's registered database could not be resolved or dialed. Ablo never falls back to shared storage for a dedicated tenant — retry, and check the datasource status if it persists."),
|
|
138
135
|
database_role_cannot_enforce_rls: wire('permission', 403, false, 'The database role Ablo connects with is a superuser or has `BYPASSRLS`, so Postgres will not enforce row-level security for it. Connect with a role that is subject to RLS.'),
|
|
139
136
|
database_role_unreadable: wire('permission', 403, false, 'Ablo could not introspect the database role it connects with, so it cannot verify that row-level security is enforced.'),
|
|
@@ -153,6 +150,7 @@ export const ERROR_CODES = {
|
|
|
153
150
|
// behind the holder (`ablo.<model>.claim`), or re-read and rebase.
|
|
154
151
|
claim_conflict: wire('claim', 409, false, 'Another participant holds a claim on this row, so the write was rejected. Take a claim with `ablo.<model>.claim` to queue fairly behind the holder, or re-read and rebase.'),
|
|
155
152
|
claim_lost: wire('claim', 409, false, 'The claim held on this row was lost before the write could apply. Re-acquire the claim and retry.'),
|
|
153
|
+
fence_token_stale: wire('claim', 409, false, 'This write carried a fencing token below the row’s current high-water: a later holder claimed the row, wrote, and moved on while this claim was lapsed, so applying the write would silently overwrite their work. The claim is gone — re-claim the row and retry from the current state.'),
|
|
156
154
|
entity_claimed: wire('claim', 409, false, 'This row is currently claimed by another participant, so the write was blocked. Queue behind the holder with `ablo.<model>.claim`, or wait for the claim to clear.'),
|
|
157
155
|
malformed_claim: wire('claim', 400, false, 'The claim payload could not be parsed. A claim must name the model and the entity it targets; check the payload shape and resend.'),
|
|
158
156
|
malformed_subscription: wire('validation', 400, false, 'The `update_subscription` payload was malformed; expected `{ syncGroups: string[] }`.'),
|
|
@@ -173,6 +171,8 @@ export const ERROR_CODES = {
|
|
|
173
171
|
contention_exhausted: client('conflict', 'A functional update kept losing to concurrent writes and exhausted its reconcile budget. Back off and retry, raise `retries`, or move the row to the WebSocket transport.'),
|
|
174
172
|
update_aborted: client('conflict', 'The functional update was aborted via its `AbortSignal` before the write landed; nothing was written.'),
|
|
175
173
|
idempotency_conflict: wire('conflict', 409, false, 'This `Idempotency-Key` was already used with a different request body. Reuse a key only to retry an identical request; otherwise generate a new one.'),
|
|
174
|
+
idempotency_key_expired: wire('conflict', 409, false, 'This idempotency key belongs to an expired retained source intent and cannot be executed again safely. Use a new key only for a genuinely new write.'),
|
|
175
|
+
source_transport_pinned: wire('conflict', 409, false, 'An earlier attempt under this idempotency key was pinned to a different source transport. Restore that route and retry the same key; Ablo will not switch a possibly committed write.'),
|
|
176
176
|
idempotency_key_too_long: wire('validation', 400, false, 'The supplied `Idempotency-Key` exceeds the maximum length. Use a shorter key — a UUID works well.'),
|
|
177
177
|
// ── validation (400 / 422) ─────────────────────────────────────────
|
|
178
178
|
write_options_invalid: client('validation', 'The write options (`idempotencyKey` / `label` / `wait` / `readAt` / `onStale` / `claim`) failed validation against the write-options schema.'),
|
|
@@ -262,9 +262,12 @@ export const ERROR_CODES = {
|
|
|
262
262
|
exchange_malformed_response: wire('transport', 502, true, 'The credential exchange returned a response that could not be parsed. Retrying may succeed.'),
|
|
263
263
|
exchange_network_error: wire('transport', 503, true, 'A network error interrupted the credential exchange. Check connectivity and retry.'),
|
|
264
264
|
source_network_error: wire('transport', 503, true, 'A network error occurred while talking to the data source. Check connectivity and retry.'),
|
|
265
|
+
source_unreachable: wire('transport', 503, true, 'Ablo could not safely reach the registered direct data source before completing the write. The write remains pinned to direct; retry the same idempotency key after connectivity recovers.'),
|
|
266
|
+
data_source_blocked: wire('transport', 503, false, 'The plane\'s data source is not in a verified, resolvable state. Fix its registration, secret, or configuration; Ablo will not fall through to hosted storage.'),
|
|
265
267
|
identity_network_error: wire('transport', 503, true, 'A network error occurred while resolving your identity. Check connectivity and retry.'),
|
|
266
268
|
commit_no_result: wire('transport', 504, true, 'The commit was sent, but no result frame arrived, so its outcome is unknown. It is safe to retry.'),
|
|
267
269
|
commit_failed: wire('transport', 500, true, 'The commit reached the server but failed to apply. Retrying may succeed.'),
|
|
270
|
+
replication_lag_timeout: wire('transport', 504, true, "The data source accepted the write, but its correlated authoritative source delta did not arrive before the confirmation deadline. The write may still materialize; retry with the same idempotency key or wait for source ingestion to recover."),
|
|
268
271
|
commit_offline_grace_expired: wire('transport', 503, false, 'The offline grace window expired before this commit could be sent, so it was not applied. Re-apply the change once the connection returns.'),
|
|
269
272
|
queue_too_deep: wire('transport', 503, true, 'The transaction queue is over its depth limit, so new writes are being rejected until it drains. Retry shortly.'),
|
|
270
273
|
flush_timeout: wire('transport', 504, true, 'Flushing the transaction queue timed out before every pending write was sent. Retry once connectivity stabilizes.'),
|
|
@@ -346,6 +349,8 @@ export const ERROR_CODES = {
|
|
|
346
349
|
invalid_id: wire('validation', 400, false, 'The id in the request is not a valid identifier.'),
|
|
347
350
|
unknown_model: wire('tenant', 400, false, 'Named a model the server does not know. Run `ablo push` (or keep `ablo dev` running) to upload `ablo/schema.ts` — the server keeps its own copy of the schema.'),
|
|
348
351
|
model_not_tenant_scoped: wire('tenant', 400, false, 'This model is not tenant-scoped, so it cannot be queried through the tenant-scoped read path.'),
|
|
352
|
+
source_tenancy_not_enforced: wire('tenant', 400, false, "This model is scoped by its connected data source (`policy: { by: 'source' }`), so its tenant is resolved from the source registration rather than a row column. Enforcing that resolution requires the write-through connect path, which is not enabled on this plane yet — so the model cannot be served through the tenant-scoped read or bootstrap path without risking a cross-tenant read. If this model lives on a log plane, scope it with `by: 'column'` or `by: 'parent'` instead."),
|
|
353
|
+
model_not_provisioned: wire('tenant', 409, false, "This model is in the plane's registered schema, but its table has not been provisioned yet. Registering a schema with `ablo push` records the model; a plane's physical tables are created separately, out of band, so a model can appear in the schema before its table exists. Provision the plane's tables, then retry the read."),
|
|
349
354
|
schema_table_invalid: wire('schema', 500, false, "The model's table identifier is invalid."),
|
|
350
355
|
schema_scope_invalid: wire('schema', 500, false, "The model's scope predicate could not be built."),
|
|
351
356
|
entity_fetch_failed: wire('server', 500, true, 'The server failed to fetch the requested entity. It is safe to retry.'),
|
package/dist/errors.js
CHANGED
|
@@ -226,11 +226,6 @@ export class AbloContentionError extends AbloError {
|
|
|
226
226
|
this.attempts = attempts;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
function claimReason(claim) {
|
|
230
|
-
if (!claim)
|
|
231
|
-
return undefined;
|
|
232
|
-
return claim.reason;
|
|
233
|
-
}
|
|
234
229
|
function claimDescription(claim) {
|
|
235
230
|
if (!claim)
|
|
236
231
|
return undefined;
|
|
@@ -238,6 +233,8 @@ function claimDescription(claim) {
|
|
|
238
233
|
return claim.description;
|
|
239
234
|
}
|
|
240
235
|
const meta = 'target' in claim ? claim.target?.meta ?? claim.meta : claim.meta;
|
|
236
|
+
// Fall back through the meta carrier so a frame that stashed its description
|
|
237
|
+
// there still renders its holder's work.
|
|
241
238
|
return descriptionFromMeta(meta);
|
|
242
239
|
}
|
|
243
240
|
function claimExpiresAt(claim) {
|
|
@@ -256,20 +253,18 @@ function secondsUntil(ms, now = Date.now()) {
|
|
|
256
253
|
}
|
|
257
254
|
export function formatClaimedErrorMessage(args) {
|
|
258
255
|
const holder = claimActor(args.claim, args.heldBy);
|
|
259
|
-
const reason = claimReason(args.claim);
|
|
260
256
|
const description = claimDescription(args.claim);
|
|
261
257
|
const expiresIn = secondsUntil(claimExpiresAt(args.claim));
|
|
262
|
-
if (!holder && !
|
|
258
|
+
if (!holder && !description) {
|
|
263
259
|
return args.fallback ?? `Model row is claimed: ${args.targetLabel}.`;
|
|
264
260
|
}
|
|
265
261
|
const actor = holder ?? 'another participant';
|
|
266
|
-
const reasonPart = reason ? ` (${reason})` : '';
|
|
267
262
|
const descriptionPart = description ? `: ${description}` : '';
|
|
268
263
|
const expiresPart = expiresIn !== undefined ? ` - expires in ${expiresIn}s` : '';
|
|
269
264
|
const policyPart = args.policyReason
|
|
270
265
|
? ` Policy reason: ${args.policyReason}.`
|
|
271
266
|
: '';
|
|
272
|
-
return `Claimed by ${actor}${
|
|
267
|
+
return `Claimed by ${actor}${descriptionPart}${expiresPart} on ${args.targetLabel}.${policyPart}`;
|
|
273
268
|
}
|
|
274
269
|
/**
|
|
275
270
|
* The target entity is currently claimed by another participant and the caller
|
package/dist/index.d.ts
CHANGED
|
@@ -63,6 +63,10 @@ export type { AbloOptions, LocalCountOptions, LocalReadOptions, ModelListScope,
|
|
|
63
63
|
* the next major release because it was explicitly documented in 0.20.1.
|
|
64
64
|
*/
|
|
65
65
|
export type { HeldClaim } from './client/Ablo.js';
|
|
66
|
+
/** The resolved value of the row-free `ablo.<model>.claim(id)` overload — a
|
|
67
|
+
* held lease on a key whose row Ablo does not hold (a {@link HeldClaim} with
|
|
68
|
+
* no `.data`). */
|
|
69
|
+
export type { HeldLease } from './client/Ablo.js';
|
|
66
70
|
export type { AbloPersistence } from './client/persistence.js';
|
|
67
71
|
export { durableWritesConfigSchema, durableWriteStoreSchema, pendingWriteSchema, } from './transactions/durableWriteStore.js';
|
|
68
72
|
export type { DurableWritesConfig, DurableWriteStore, PendingWrite, } from './transactions/durableWriteStore.js';
|