@abloatai/ablo 0.29.3 → 0.30.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/CHANGELOG.md +21 -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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical runtime contracts for commit settlement.
|
|
3
|
+
*
|
|
4
|
+
* A commit crosses several boundaries during its lifetime: the server's
|
|
5
|
+
* execution cache, the HTTP/WS receipt, and the client's acknowledgement
|
|
6
|
+
* tracker. Those boundaries intentionally have different envelopes, but they
|
|
7
|
+
* all compose the same settlement vocabulary from this module:
|
|
8
|
+
*
|
|
9
|
+
* - `confirmed` — the authoritative change is visible at a sync watermark.
|
|
10
|
+
* - `queued` — a connected source durably accepted the write and only its
|
|
11
|
+
* correlated authoritative source delta (WAL for direct, endpoint event
|
|
12
|
+
* for endpoint-only), identified by `correlationId`, may promote it.
|
|
13
|
+
*
|
|
14
|
+
* Keeping the variants discriminated here makes an impossible receipt (most
|
|
15
|
+
* importantly, `queued` without a correlation) unrepresentable at every
|
|
16
|
+
* untrusted boundary without collapsing the distinct lifecycle envelopes into
|
|
17
|
+
* one giant object.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { staleNotificationSchema } from '../coordination/schema.js';
|
|
21
|
+
/** Matches the permanent source/idempotency-key ceiling. */
|
|
22
|
+
export const COMMIT_CORRELATION_ID_MAX_LENGTH = 255;
|
|
23
|
+
/** Opaque server-authored identity shared by a queued receipt and its authoritative source delta. */
|
|
24
|
+
export const correlationIdSchema = z.string().min(1).max(COMMIT_CORRELATION_ID_MAX_LENGTH);
|
|
25
|
+
export const commitStatusSchema = z.enum(['queued', 'confirmed']);
|
|
26
|
+
const queuedSettlementShape = {
|
|
27
|
+
status: z.literal('queued'),
|
|
28
|
+
correlationId: correlationIdSchema,
|
|
29
|
+
};
|
|
30
|
+
const confirmedSettlementShape = {
|
|
31
|
+
status: z.literal('confirmed'),
|
|
32
|
+
correlationId: correlationIdSchema.optional(),
|
|
33
|
+
};
|
|
34
|
+
const queuedSettlementSchema = z.strictObject(queuedSettlementShape);
|
|
35
|
+
const confirmedSettlementSchema = z.strictObject(confirmedSettlementShape);
|
|
36
|
+
/** The one settlement discriminant shared by every commit boundary. */
|
|
37
|
+
export const commitSettlementSchema = z.discriminatedUnion('status', [
|
|
38
|
+
queuedSettlementSchema,
|
|
39
|
+
confirmedSettlementSchema,
|
|
40
|
+
]);
|
|
41
|
+
const missingIdsSchema = z.array(z.string().min(1));
|
|
42
|
+
const notificationsSchema = z.array(staleNotificationSchema);
|
|
43
|
+
const successfulReceiptCommonShape = {
|
|
44
|
+
object: z.literal('commit_receipt'),
|
|
45
|
+
/** Optional convenience id used by some HTTP surfaces. */
|
|
46
|
+
id: z.string().min(1).optional(),
|
|
47
|
+
clientTxId: z.string().min(1),
|
|
48
|
+
serverTxId: z.string().min(1),
|
|
49
|
+
success: z.literal(true),
|
|
50
|
+
lastSyncId: z.number().int().nonnegative(),
|
|
51
|
+
ops: z.number().int().nonnegative(),
|
|
52
|
+
notifications: notificationsSchema.optional(),
|
|
53
|
+
missingIds: missingIdsSchema.optional(),
|
|
54
|
+
};
|
|
55
|
+
const queuedCommitReceiptSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
...successfulReceiptCommonShape,
|
|
58
|
+
...queuedSettlementShape,
|
|
59
|
+
})
|
|
60
|
+
.superRefine((receipt, context) => {
|
|
61
|
+
if (receipt.lastSyncId !== 0) {
|
|
62
|
+
context.addIssue({
|
|
63
|
+
code: 'custom',
|
|
64
|
+
path: ['lastSyncId'],
|
|
65
|
+
message: 'A queued source receipt cannot claim a confirmed sync watermark',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const confirmedCommitReceiptSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
...successfulReceiptCommonShape,
|
|
72
|
+
...confirmedSettlementShape,
|
|
73
|
+
})
|
|
74
|
+
.superRefine((receipt, context) => {
|
|
75
|
+
if (receipt.correlationId && receipt.lastSyncId <= 0) {
|
|
76
|
+
context.addIssue({
|
|
77
|
+
code: 'custom',
|
|
78
|
+
path: ['lastSyncId'],
|
|
79
|
+
message: 'A source-confirmed receipt requires a positive sync watermark',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
/**
|
|
84
|
+
* Successful HTTP/WS receipt. This schema is deliberately strict about the
|
|
85
|
+
* settlement discriminant. Zod's default object behavior strips additive
|
|
86
|
+
* fields, which is forward compatible without allowing server-internal
|
|
87
|
+
* recovery evidence to leak through a parsed wire receipt.
|
|
88
|
+
*/
|
|
89
|
+
export const commitReceiptSchema = z.discriminatedUnion('status', [
|
|
90
|
+
queuedCommitReceiptSchema,
|
|
91
|
+
confirmedCommitReceiptSchema,
|
|
92
|
+
]);
|
|
93
|
+
/**
|
|
94
|
+
* Compatibility decoder for pre-contract WebSocket acknowledgements. It
|
|
95
|
+
* supplies fields old servers omitted and normalizes their string sync ids,
|
|
96
|
+
* then hands the result to the exact same canonical receipt schema. It never
|
|
97
|
+
* invents a source correlation: an old/malformed queued receipt still fails
|
|
98
|
+
* closed.
|
|
99
|
+
*/
|
|
100
|
+
export const legacyCompatibleCommitReceiptSchema = z.preprocess((value) => {
|
|
101
|
+
if (typeof value !== 'object' || value === null)
|
|
102
|
+
return value;
|
|
103
|
+
const receipt = value;
|
|
104
|
+
if (receipt.success !== true)
|
|
105
|
+
return value;
|
|
106
|
+
const rawLastSyncId = receipt.lastSyncId;
|
|
107
|
+
const numericLastSyncId = typeof rawLastSyncId === 'string' && rawLastSyncId.trim().length > 0
|
|
108
|
+
? Number(rawLastSyncId)
|
|
109
|
+
: rawLastSyncId;
|
|
110
|
+
const normalizedLastSyncId = typeof numericLastSyncId === 'number' &&
|
|
111
|
+
Number.isSafeInteger(numericLastSyncId) &&
|
|
112
|
+
numericLastSyncId >= 0
|
|
113
|
+
? numericLastSyncId
|
|
114
|
+
: undefined;
|
|
115
|
+
return {
|
|
116
|
+
...receipt,
|
|
117
|
+
object: receipt.object ?? 'commit_receipt',
|
|
118
|
+
status: receipt.status ?? 'confirmed',
|
|
119
|
+
serverTxId: typeof receipt.serverTxId === 'string'
|
|
120
|
+
? receipt.serverTxId
|
|
121
|
+
: String(normalizedLastSyncId ?? 0),
|
|
122
|
+
ops: typeof receipt.ops === 'number' && Number.isSafeInteger(receipt.ops) ? receipt.ops : 0,
|
|
123
|
+
lastSyncId: normalizedLastSyncId ?? 0,
|
|
124
|
+
};
|
|
125
|
+
}, commitReceiptSchema);
|
|
126
|
+
const requiredCapabilitySchema = z.custom((value) => typeof value === 'object' &&
|
|
127
|
+
value !== null &&
|
|
128
|
+
typeof value.scope === 'string', { message: 'requiredCapability must contain a scope' });
|
|
129
|
+
const errorCodeSchema = z.custom((value) => typeof value === 'string' && value.length > 0, { message: 'commit rejection code must be a non-empty string' });
|
|
130
|
+
/** The failure arm is separate: queued/confirmed always imply `success:true`. */
|
|
131
|
+
export const rejectedCommitReceiptSchema = z.object({
|
|
132
|
+
object: z.literal('commit_receipt'),
|
|
133
|
+
clientTxId: z.string(),
|
|
134
|
+
serverTxId: z.string(),
|
|
135
|
+
success: z.literal(false),
|
|
136
|
+
status: z.literal('rejected'),
|
|
137
|
+
lastSyncId: z.number().int().nonnegative().optional(),
|
|
138
|
+
ops: z.number().int().nonnegative().optional(),
|
|
139
|
+
error: z.object({
|
|
140
|
+
code: errorCodeSchema,
|
|
141
|
+
message: z.string(),
|
|
142
|
+
field: z.string().optional(),
|
|
143
|
+
requiredCapability: requiredCapabilitySchema.optional(),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
export const mutationResultPayloadSchema = z.union([
|
|
147
|
+
commitReceiptSchema,
|
|
148
|
+
rejectedCommitReceiptSchema,
|
|
149
|
+
]);
|
|
150
|
+
export const mutationResultMessageSchema = z.object({
|
|
151
|
+
type: z.literal('mutation_result'),
|
|
152
|
+
payload: mutationResultPayloadSchema,
|
|
153
|
+
});
|
|
154
|
+
const confirmationTransactionIdsSchema = z
|
|
155
|
+
.array(z.string().min(1))
|
|
156
|
+
.min(1)
|
|
157
|
+
.refine((ids) => new Set(ids).size === ids.length, {
|
|
158
|
+
message: 'confirmation transaction ids must be unique',
|
|
159
|
+
});
|
|
160
|
+
/**
|
|
161
|
+
* Server-internal execution result persisted in `mutation_log`.
|
|
162
|
+
*
|
|
163
|
+
* `status` remains optional only for pre-settlement hosted rows. At runtime an
|
|
164
|
+
* omitted value has the established meaning `confirmed`; new source-forwarded
|
|
165
|
+
* rows must use the explicit queued arm with both public correlation and exact
|
|
166
|
+
* internal storage ids.
|
|
167
|
+
*/
|
|
168
|
+
const rawCommitExecutionResultSchema = z
|
|
169
|
+
.strictObject({
|
|
170
|
+
lastSyncId: z.number().int().nonnegative(),
|
|
171
|
+
firstSyncId: z.number().int().nonnegative(),
|
|
172
|
+
status: commitStatusSchema.optional(),
|
|
173
|
+
correlationId: correlationIdSchema.optional(),
|
|
174
|
+
confirmationTransactionIds: confirmationTransactionIdsSchema.optional(),
|
|
175
|
+
notifications: notificationsSchema.optional(),
|
|
176
|
+
missingIds: missingIdsSchema.optional(),
|
|
177
|
+
})
|
|
178
|
+
.superRefine((result, context) => {
|
|
179
|
+
const validRange = (result.firstSyncId === 0 && result.lastSyncId === 0) ||
|
|
180
|
+
(result.firstSyncId > 0 && result.firstSyncId <= result.lastSyncId);
|
|
181
|
+
if (!validRange) {
|
|
182
|
+
context.addIssue({
|
|
183
|
+
code: 'custom',
|
|
184
|
+
path: ['firstSyncId'],
|
|
185
|
+
message: 'CommitResult sync range is inconsistent',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (result.status === 'queued') {
|
|
189
|
+
if (!result.correlationId) {
|
|
190
|
+
context.addIssue({
|
|
191
|
+
code: 'custom',
|
|
192
|
+
path: ['correlationId'],
|
|
193
|
+
message: 'A queued source result requires a correlationId',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (!result.confirmationTransactionIds) {
|
|
197
|
+
context.addIssue({
|
|
198
|
+
code: 'custom',
|
|
199
|
+
path: ['confirmationTransactionIds'],
|
|
200
|
+
message: 'A queued source result requires durable confirmation ids',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (result.firstSyncId !== 0 || result.lastSyncId !== 0) {
|
|
204
|
+
context.addIssue({
|
|
205
|
+
code: 'custom',
|
|
206
|
+
path: ['lastSyncId'],
|
|
207
|
+
message: 'A queued source result cannot claim a confirmed sync range',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (result.confirmationTransactionIds && !result.correlationId) {
|
|
212
|
+
context.addIssue({
|
|
213
|
+
code: 'custom',
|
|
214
|
+
path: ['correlationId'],
|
|
215
|
+
message: 'Confirmation transaction ids require their source correlation',
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (result.correlationId && !result.confirmationTransactionIds) {
|
|
219
|
+
context.addIssue({
|
|
220
|
+
code: 'custom',
|
|
221
|
+
path: ['confirmationTransactionIds'],
|
|
222
|
+
message: 'A source correlation requires exact confirmation transaction ids',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (result.correlationId && result.status === undefined) {
|
|
226
|
+
context.addIssue({
|
|
227
|
+
code: 'custom',
|
|
228
|
+
path: ['status'],
|
|
229
|
+
message: 'Source-correlated results require explicit settlement status',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (result.status === 'confirmed' &&
|
|
233
|
+
result.correlationId &&
|
|
234
|
+
(result.firstSyncId <= 0 || result.lastSyncId <= 0)) {
|
|
235
|
+
context.addIssue({
|
|
236
|
+
code: 'custom',
|
|
237
|
+
path: ['lastSyncId'],
|
|
238
|
+
message: 'A source-confirmed result requires a durable positive sync range',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
export const commitExecutionResultSchema = rawCommitExecutionResultSchema.transform((result) => ({
|
|
243
|
+
...result,
|
|
244
|
+
status: result.status ?? 'confirmed',
|
|
245
|
+
}));
|
|
246
|
+
const ackCommonShape = {
|
|
247
|
+
lastSyncId: z.number().int().nonnegative(),
|
|
248
|
+
notifications: notificationsSchema.optional(),
|
|
249
|
+
missingIds: missingIdsSchema.optional(),
|
|
250
|
+
};
|
|
251
|
+
/** Normalized acknowledgement handed from a mutation transport to the queue. */
|
|
252
|
+
export const commitAckSchema = z.discriminatedUnion('status', [
|
|
253
|
+
z
|
|
254
|
+
.strictObject({
|
|
255
|
+
...ackCommonShape,
|
|
256
|
+
...queuedSettlementShape,
|
|
257
|
+
})
|
|
258
|
+
.refine(({ lastSyncId }) => lastSyncId === 0, {
|
|
259
|
+
path: ['lastSyncId'],
|
|
260
|
+
message: 'A queued acknowledgement cannot claim a sync watermark',
|
|
261
|
+
}),
|
|
262
|
+
z
|
|
263
|
+
.strictObject({
|
|
264
|
+
...ackCommonShape,
|
|
265
|
+
...confirmedSettlementShape,
|
|
266
|
+
})
|
|
267
|
+
.refine(({ correlationId, lastSyncId }) => correlationId === undefined || lastSyncId > 0, {
|
|
268
|
+
path: ['lastSyncId'],
|
|
269
|
+
message: 'A source-confirmed acknowledgement requires a sync watermark',
|
|
270
|
+
}),
|
|
271
|
+
]);
|
|
272
|
+
const legacyMutationCommitResultSchema = z.strictObject({
|
|
273
|
+
...ackCommonShape,
|
|
274
|
+
status: z.undefined().optional(),
|
|
275
|
+
correlationId: z.undefined().optional(),
|
|
276
|
+
});
|
|
277
|
+
/**
|
|
278
|
+
* Compatibility boundary for injected/older mutation executors. New
|
|
279
|
+
* transports return `commitAckSchema`; a legacy `{lastSyncId}` is normalized
|
|
280
|
+
* to explicit confirmed settlement before queue code branches on it.
|
|
281
|
+
*/
|
|
282
|
+
export const mutationCommitResultSchema = z
|
|
283
|
+
.union([commitAckSchema, legacyMutationCommitResultSchema])
|
|
284
|
+
.transform((result) => (result.status ? result : { ...result, status: 'confirmed' }));
|
|
285
|
+
/**
|
|
286
|
+
* Public SDK projection returned by `ablo.commits` and model mutations.
|
|
287
|
+
*
|
|
288
|
+
* This intentionally does not compose `commitSettlementSchema`: on the WS
|
|
289
|
+
* facade, `wait:'queued'` currently means locally sealed/enqueued, before a
|
|
290
|
+
* server has necessarily accepted the write. Renaming that public state to
|
|
291
|
+
* `enqueued` is a separate protocol change; the authoritative settlement
|
|
292
|
+
* union above is reserved for server acknowledgements.
|
|
293
|
+
*/
|
|
294
|
+
export const clientCommitReceiptSchema = z.strictObject({
|
|
295
|
+
id: z.string().min(1),
|
|
296
|
+
status: commitStatusSchema,
|
|
297
|
+
lastSyncId: z.number().int().nonnegative().optional(),
|
|
298
|
+
notifications: notificationsSchema.optional(),
|
|
299
|
+
missingIds: missingIdsSchema.optional(),
|
|
300
|
+
});
|
package/dist/wire/delta.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export declare const syncDeltaWireCoreSchema: z.ZodObject<{
|
|
|
105
105
|
previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
|
|
106
106
|
syncGroups: z.ZodArray<z.ZodString>;
|
|
107
107
|
transactionId: z.ZodOptional<z.ZodString>;
|
|
108
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
108
109
|
createdAt: z.ZodString;
|
|
109
110
|
}, z.core.$strip>;
|
|
110
111
|
export type SyncDeltaWireCore = z.infer<typeof syncDeltaWireCoreSchema>;
|
|
@@ -130,6 +131,7 @@ export declare const clientSyncDeltaSchema: z.ZodObject<{
|
|
|
130
131
|
previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
|
|
131
132
|
syncGroups: z.ZodArray<z.ZodString>;
|
|
132
133
|
transactionId: z.ZodOptional<z.ZodString>;
|
|
134
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
133
135
|
createdAt: z.ZodString;
|
|
134
136
|
createdBy: z.ZodOptional<z.ZodString>;
|
|
135
137
|
metadata: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
|
|
@@ -158,7 +160,9 @@ export declare const serverSyncDeltaSchema: z.ZodObject<{
|
|
|
158
160
|
data: z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>;
|
|
159
161
|
previousData: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodUnknown>, z.ZodString]>>>;
|
|
160
162
|
syncGroups: z.ZodArray<z.ZodString>;
|
|
163
|
+
correlationId: z.ZodOptional<z.ZodString>;
|
|
161
164
|
createdAt: z.ZodString;
|
|
165
|
+
projectId: z.ZodString;
|
|
162
166
|
transactionId: z.ZodNullable<z.ZodString>;
|
|
163
167
|
createdBy: z.ZodNullable<z.ZodObject<{
|
|
164
168
|
kind: z.ZodEnum<{
|
package/dist/wire/delta.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* up into the schema DSL for them.
|
|
25
25
|
*/
|
|
26
26
|
import { z } from 'zod';
|
|
27
|
+
import { correlationIdSchema } from './commit.js';
|
|
27
28
|
// ── Shared participant vocabulary (mirrors the corresponding Postgres enums) ──
|
|
28
29
|
/** `participant_kind` — who a delta is attributed to. */
|
|
29
30
|
export const participantKindSchema = z.enum(['user', 'agent', 'system']);
|
|
@@ -78,6 +79,8 @@ export const syncDeltaWireCoreSchema = z.object({
|
|
|
78
79
|
previousData: wireDeltaDataSchema.optional(),
|
|
79
80
|
syncGroups: z.array(z.string()),
|
|
80
81
|
transactionId: z.string().optional(),
|
|
82
|
+
/** Opaque source-batch identity present only on decoded customer-WAL echoes. */
|
|
83
|
+
correlationId: correlationIdSchema.optional(),
|
|
81
84
|
createdAt: z.string(),
|
|
82
85
|
});
|
|
83
86
|
/**
|
|
@@ -99,6 +102,12 @@ export const clientSyncDeltaSchema = syncDeltaWireCoreSchema.extend({
|
|
|
99
102
|
* projection also narrows `transactionId` to nullable, matching the stored column.
|
|
100
103
|
*/
|
|
101
104
|
export const serverSyncDeltaSchema = syncDeltaWireCoreSchema.extend({
|
|
105
|
+
/** The plane project the delta was committed on (`''` = the org's default
|
|
106
|
+
* project). Server-only: the broadcaster uses it to fan a mixed-project batch
|
|
107
|
+
* out ONLY to same-project subscribers. It rides on the frame but the client's
|
|
108
|
+
* `clientSyncDeltaSchema` (a non-strict object) strips it — a client only ever
|
|
109
|
+
* receives its own project's deltas, so the value is redundant to it. */
|
|
110
|
+
projectId: z.string(),
|
|
102
111
|
transactionId: z.string().nullable(),
|
|
103
112
|
/** @deprecated Duplicates `actor`; read `actor` instead. */
|
|
104
113
|
createdBy: participantRefSchema.nullable(),
|
package/dist/wire/frames.d.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* server to update together.
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
-
import type { OnStaleMode,
|
|
17
|
-
import type {
|
|
16
|
+
import type { OnStaleMode, ReadDependency } from '../coordination/index.js';
|
|
17
|
+
import type { MutationResultMessageWire } from './commit.js';
|
|
18
18
|
/**
|
|
19
19
|
* A single operation within a {@link CommitMessage} batch. Each operation is
|
|
20
20
|
* the smallest unit the server applies atomically — one create, update,
|
|
@@ -55,6 +55,15 @@ export interface CommitOperation {
|
|
|
55
55
|
* framework identities; a bypass requested by an agent is ignored.
|
|
56
56
|
*/
|
|
57
57
|
bypass?: boolean | null;
|
|
58
|
+
/**
|
|
59
|
+
* The monotonic fencing token of the held claim this write was issued under
|
|
60
|
+
* ({@link https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html Kleppmann fencing}).
|
|
61
|
+
* The server rejects the write if a later holder already advanced this
|
|
62
|
+
* entity's persisted high-water past the token — closing the "lapsed holder
|
|
63
|
+
* resumes after its successor came and went" residual that the live-lease
|
|
64
|
+
* claim guard cannot see. Absent on every unclaimed write.
|
|
65
|
+
*/
|
|
66
|
+
fenceToken?: number | null;
|
|
58
67
|
}
|
|
59
68
|
/**
|
|
60
69
|
* Runtime validator for {@link CommitOperation}. Both commit transports — the
|
|
@@ -83,6 +92,7 @@ export declare const commitOperationSchema: z.ZodObject<{
|
|
|
83
92
|
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
84
93
|
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
85
94
|
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
95
|
+
fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
86
96
|
bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
87
97
|
}, z.core.$strip>;
|
|
88
98
|
/**
|
|
@@ -138,6 +148,7 @@ export declare const commitPayloadSchema: z.ZodObject<{
|
|
|
138
148
|
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
139
149
|
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
140
150
|
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
151
|
+
fenceToken: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
141
152
|
bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
142
153
|
}, z.core.$strip>>;
|
|
143
154
|
clientTxId: z.ZodString;
|
|
@@ -163,47 +174,8 @@ export declare const commitPayloadSchema: z.ZodObject<{
|
|
|
163
174
|
}, z.core.$strip>]>>>>;
|
|
164
175
|
}, z.core.$strip>;
|
|
165
176
|
/**
|
|
166
|
-
* The server's acknowledgement of a {@link CommitMessage}.
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* carry the same fields.
|
|
170
|
-
*
|
|
171
|
-
* `object`, `status`, and `ops` are optional in the type but the server always
|
|
172
|
-
* populates them, so a current client can rely on them being present.
|
|
177
|
+
* The server's acknowledgement of a {@link CommitMessage}. Runtime shape and
|
|
178
|
+
* TypeScript type are both owned by `wire/commit.ts`; HTTP, WebSocket, and
|
|
179
|
+
* cached replay no longer maintain parallel receipt declarations.
|
|
173
180
|
*/
|
|
174
|
-
export
|
|
175
|
-
type: 'mutation_result';
|
|
176
|
-
payload: {
|
|
177
|
-
object?: 'commit_receipt';
|
|
178
|
-
clientTxId: string;
|
|
179
|
-
serverTxId: string;
|
|
180
|
-
success: boolean;
|
|
181
|
-
status?: 'confirmed' | 'rejected';
|
|
182
|
-
lastSyncId?: number;
|
|
183
|
-
ops?: number;
|
|
184
|
-
/**
|
|
185
|
-
* Notifications for operations that used `onStale: 'notify'` and whose
|
|
186
|
-
* premise changed while the batch was being applied. Present only on a
|
|
187
|
-
* successful acknowledgement that resolved such a conflict; the client
|
|
188
|
-
* surfaces each one through its `conflict:notified` event and the commit
|
|
189
|
-
* receipt rather than failing the write.
|
|
190
|
-
*/
|
|
191
|
-
notifications?: StaleNotification[];
|
|
192
|
-
/**
|
|
193
|
-
* Ids of update or delete targets that matched no rows — because they do
|
|
194
|
-
* not exist or fall outside the caller's organization. Present and
|
|
195
|
-
* non-empty only when a write missed. The client raises a not-found error
|
|
196
|
-
* for the affected caller rather than treating the no-op as a success.
|
|
197
|
-
*/
|
|
198
|
-
missingIds?: string[];
|
|
199
|
-
error?: {
|
|
200
|
-
code: ErrorCode;
|
|
201
|
-
message: string;
|
|
202
|
-
field?: string;
|
|
203
|
-
/** The capability the commit required but the caller lacked. Present when
|
|
204
|
-
* the commit was denied for want of a capability, so the client can tell
|
|
205
|
-
* the caller exactly what to obtain. */
|
|
206
|
-
requiredCapability?: RequiredCapability;
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
}
|
|
181
|
+
export type MutationResultMessage = MutationResultMessageWire;
|
package/dist/wire/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
|
|
|
25
25
|
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
|
|
26
26
|
export type { SupportedProtocolVersion, ProtocolVersionProblem, } from './protocolVersion.js';
|
|
27
27
|
export type { CommitOperation, CommitMessage, MutationResultMessage, } from './frames.js';
|
|
28
|
+
export { COMMIT_CORRELATION_ID_MAX_LENGTH, correlationIdSchema, commitStatusSchema, commitSettlementSchema, commitReceiptSchema, legacyCompatibleCommitReceiptSchema, rejectedCommitReceiptSchema, mutationResultPayloadSchema, mutationResultMessageSchema, commitAckSchema, mutationCommitResultSchema, } from './commit.js';
|
|
29
|
+
export type { CorrelationId, CommitStatus, CommitSettlement, CommitReceiptWire, RejectedCommitReceiptWire, MutationResultPayload, MutationResultMessageWire, CommitAck, MutationCommitResultInput, MutationCommitResult, } from './commit.js';
|
|
28
30
|
export { participantKindSchema, confirmationStateSchema, syncDeltaActionSchema, wireDeltaDataSchema, participantRefSchema, syncDeltaWireCoreSchema, clientSyncDeltaSchema, serverSyncDeltaSchema, } from './delta.js';
|
|
29
31
|
export type { ParticipantKind, ConfirmationState, SyncDeltaAction, WireDeltaData, ParticipantRef, SyncDeltaWireCore, ClientSyncDelta, ServerSyncDelta, } from './delta.js';
|
|
30
32
|
export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, errorCodeSpec, } from '../errors.js';
|
package/dist/wire/index.js
CHANGED
|
@@ -27,6 +27,10 @@ export { commitOperationSchema, commitPayloadSchema, } from './frames.js';
|
|
|
27
27
|
// confirm they can speak to each other, plus the WebSocket close code used to
|
|
28
28
|
// reject a mismatch. See protocolVersion.ts for the changelog and deploy rules.
|
|
29
29
|
export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, DEFAULT_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, resolveProtocolVersion, } from './protocolVersion.js';
|
|
30
|
+
// Commit settlement backbone. The transport receipt, server execution cache,
|
|
31
|
+
// and normalized client acknowledgement are different envelopes composed from
|
|
32
|
+
// this one discriminated settlement vocabulary.
|
|
33
|
+
export { COMMIT_CORRELATION_ID_MAX_LENGTH, correlationIdSchema, commitStatusSchema, commitSettlementSchema, commitReceiptSchema, legacyCompatibleCommitReceiptSchema, rejectedCommitReceiptSchema, mutationResultPayloadSchema, mutationResultMessageSchema, commitAckSchema, mutationCommitResultSchema, } from './commit.js';
|
|
30
34
|
// The read-path delta contract: the shape the server broadcasts to clients as the
|
|
31
35
|
// payload of a `delta` or `sync_response` frame, together with the shared
|
|
32
36
|
// participant vocabulary it carries. Both ends derive their delta type from these
|
package/docs/agent-messaging.md
CHANGED
|
@@ -61,22 +61,21 @@ npx ablo push
|
|
|
61
61
|
|
|
62
62
|
## Server-side agent
|
|
63
63
|
|
|
64
|
-
Most server-side agents use the
|
|
65
|
-
|
|
64
|
+
Most server-side agents use the stateless HTTP transport. Pass the schema, API
|
|
65
|
+
key, and transport selector:
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
const ablo = Ablo({
|
|
69
69
|
schema,
|
|
70
70
|
apiKey: process.env.ABLO_API_KEY,
|
|
71
|
-
databaseUrl: process.env.DATABASE_URL,
|
|
72
71
|
transport: "http",
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
await ablo.ready();
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
`
|
|
79
|
-
the default WebSocket transport with a minted user/session token.
|
|
77
|
+
The secret `apiKey` is server-only. Browser clients must not receive it; live UIs
|
|
78
|
+
use the default WebSocket transport with a minted user/session token.
|
|
80
79
|
|
|
81
80
|
If your backend mints restricted agent tokens, register the database once from a
|
|
82
81
|
secret-key server process as above. Workers using the restricted token can then
|
package/docs/agents.md
CHANGED
|
@@ -75,8 +75,7 @@ reconnects or be readable by HTTP agents. The recipe is a `messages` model
|
|
|
75
75
|
scoped by the same syncGroup field as the work row, with `aboutIntentId` linking
|
|
76
76
|
a message back to the claim it discusses.
|
|
77
77
|
|
|
78
|
-
See [Agent Messaging](/agent-messaging) for the schema and
|
|
79
|
-
`databaseUrl` setup.
|
|
78
|
+
See [Agent Messaging](/agent-messaging) for the schema and setup details.
|
|
80
79
|
|
|
81
80
|
## Humans + agents on one state
|
|
82
81
|
|
package/docs/api-keys.md
CHANGED
|
@@ -47,7 +47,7 @@ tab-focus / network-online / device-wake). This is the Stripe ephemeral-key / Su
|
|
|
47
47
|
session model. For a read-only app you don't need any of this — just the `pk_` above.
|
|
48
48
|
|
|
49
49
|
Server-side, because `apiKey` defaults to `process.env.ABLO_API_KEY`, most backend and agent
|
|
50
|
-
code passes nothing. The secret `sk_`
|
|
50
|
+
code passes nothing. The secret `sk_` is **server-only** — never in a
|
|
51
51
|
browser bundle. There is no `getToken` or `as` option — `apiKey` (the key a server holds)
|
|
52
52
|
and `authEndpoint` (the mint route a browser points at) are the two credential
|
|
53
53
|
knobs, and you set exactly one.
|
|
@@ -58,7 +58,6 @@ knobs, and you set exactly one.
|
|
|
58
58
|
|---|---|---|
|
|
59
59
|
| Human end-user session | `await server.sessions.create({ user: { id } })` | `ek_` (full user authority) |
|
|
60
60
|
| Scoped delegated agent | `await server.sessions.create({ agent: { id }, can: { Task: ['update'] } })` | `rk_` (scoped to `can`) |
|
|
61
|
-
| Connect Ablo to your own Postgres | `Ablo({ schema, apiKey, databaseUrl })` (server-only) | dedicated tenant |
|
|
62
61
|
|
|
63
62
|
The principal kind comes from *which* shape you pass — `{ user }` → `user`, `{ agent, can }` → `agent`.
|
|
64
63
|
|
package/docs/client-behavior.md
CHANGED
|
@@ -29,7 +29,6 @@ Common options:
|
|
|
29
29
|
|---|---|
|
|
30
30
|
| `schema` | Required for typed model clients. |
|
|
31
31
|
| `apiKey` | Bearer credential for trusted server runtimes. Defaults to `ABLO_API_KEY` when available. |
|
|
32
|
-
| `databaseUrl` | Optional, server-only. Registers your Postgres directly (the connection-string path). Pass it explicitly — it is **not** auto-read from the environment. Omit it for a signed Data Source endpoint or the hosted sandbox. The SDK throws if it sees this in a browser. |
|
|
33
32
|
| `baseURL` | Override the hosted sync endpoint for staging or private deployments. |
|
|
34
33
|
| `persistence` | `memory` by default. Use `indexeddb` for a durable browser cache that survives reloads. |
|
|
35
34
|
| `durableWrites` | Optional crash recovery for unacknowledged agent/worker writes. Independent of the default memory cache; accepts `{ store, namespace? }`. |
|
|
@@ -39,11 +38,10 @@ Common options:
|
|
|
39
38
|
| `defaultQuery` | Extra query parameters attached to every HTTP request. |
|
|
40
39
|
| `dangerouslyAllowBrowser` | Required before sending an API key from browser code. Prefer a server route instead. |
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[
|
|
46
|
-
sandbox.
|
|
41
|
+
Your database connects out of band — through logical replication (`npx ablo
|
|
42
|
+
connect`) or a signed [Data Source](./data-sources.md) endpoint — so the client
|
|
43
|
+
holds only `apiKey`, never a connection string. See
|
|
44
|
+
[Connect Your Database](./data-sources.md) for the full setup.
|
|
47
45
|
|
|
48
46
|
## Model Methods
|
|
49
47
|
|