@abloatai/ablo 0.27.0 → 0.28.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +29 -3
  2. package/README.md +1 -1
  3. package/dist/BaseSyncedStore.js +8 -9
  4. package/dist/Database.d.ts +14 -1
  5. package/dist/Database.js +225 -28
  6. package/dist/Model.d.ts +17 -0
  7. package/dist/Model.js +32 -1
  8. package/dist/SyncClient.d.ts +10 -6
  9. package/dist/SyncClient.js +379 -76
  10. package/dist/adapters/inMemoryStorage.d.ts +1 -0
  11. package/dist/adapters/inMemoryStorage.js +12 -0
  12. package/dist/cli.cjs +6 -2
  13. package/dist/client/Ablo.d.ts +24 -1
  14. package/dist/client/Ablo.js +4 -3
  15. package/dist/client/ApiClient.js +309 -43
  16. package/dist/client/createInternalComponents.d.ts +2 -0
  17. package/dist/client/createInternalComponents.js +1 -1
  18. package/dist/client/httpClient.d.ts +2 -0
  19. package/dist/client/modelRegistration.js +11 -0
  20. package/dist/client/options.d.ts +23 -0
  21. package/dist/client/wsMutationExecutor.d.ts +3 -2
  22. package/dist/client/wsMutationExecutor.js +3 -2
  23. package/dist/commit/contract.d.ts +493 -0
  24. package/dist/commit/contract.js +187 -0
  25. package/dist/commit/index.d.ts +6 -0
  26. package/dist/commit/index.js +5 -0
  27. package/dist/core/StoreManager.d.ts +2 -0
  28. package/dist/core/StoreManager.js +12 -0
  29. package/dist/errorCodes.js +6 -2
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +2 -0
  32. package/dist/interfaces/index.d.ts +2 -2
  33. package/dist/mutators/UndoManager.d.ts +2 -0
  34. package/dist/mutators/UndoManager.js +32 -0
  35. package/dist/react/useAblo.d.ts +6 -4
  36. package/dist/react/useAblo.js +25 -3
  37. package/dist/schema/index.d.ts +1 -1
  38. package/dist/schema/schema.d.ts +31 -1
  39. package/dist/stores/ObjectStore.d.ts +14 -1
  40. package/dist/stores/ObjectStore.js +27 -4
  41. package/dist/stores/ObjectStoreContract.d.ts +2 -0
  42. package/dist/surface.d.ts +1 -1
  43. package/dist/surface.js +2 -0
  44. package/dist/sync/SyncWebSocket.d.ts +2 -1
  45. package/dist/sync/persistedPrefix.d.ts +12 -0
  46. package/dist/sync/persistedPrefix.js +22 -0
  47. package/dist/testing/index.d.ts +2 -0
  48. package/dist/testing/index.js +1 -0
  49. package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
  50. package/dist/testing/mocks/FakeDatabase.js +10 -0
  51. package/dist/testing/mocks/MockWebSocket.d.ts +2 -1
  52. package/dist/transactions/TransactionQueue.d.ts +66 -8
  53. package/dist/transactions/TransactionQueue.js +607 -89
  54. package/dist/transactions/commitEnvelope.d.ts +132 -0
  55. package/dist/transactions/commitEnvelope.js +139 -0
  56. package/dist/transactions/commitOutboxStore.d.ts +32 -0
  57. package/dist/transactions/commitOutboxStore.js +26 -0
  58. package/dist/transactions/commitPayload.d.ts +15 -0
  59. package/dist/transactions/commitPayload.js +6 -0
  60. package/dist/transactions/httpCommitEnvelope.d.ts +43 -0
  61. package/dist/transactions/httpCommitEnvelope.js +179 -0
  62. package/dist/transactions/replayValidation.d.ts +83 -0
  63. package/dist/transactions/replayValidation.js +46 -1
  64. package/dist/wire/bootstrapReason.d.ts +9 -0
  65. package/dist/wire/bootstrapReason.js +8 -0
  66. package/dist/wire/frames.d.ts +236 -0
  67. package/dist/wire/frames.js +21 -0
  68. package/dist/wire/index.d.ts +4 -2
  69. package/dist/wire/index.js +2 -1
  70. package/docs/api.md +10 -10
  71. package/docs/coordination.md +2 -2
  72. package/docs/mcp.md +1 -1
  73. package/package.json +7 -2
@@ -45,6 +45,14 @@ export declare const persistedTransactionSchema: z.ZodObject<{
45
45
  }, z.core.$strip>;
46
46
  createdAt: z.ZodOptional<z.ZodNumber>;
47
47
  batchId: z.ZodOptional<z.ZodString>;
48
+ commitEnvelope: z.ZodOptional<z.ZodObject<{
49
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
50
+ operationIndex: z.ZodNumber;
51
+ operationCount: z.ZodNumber;
52
+ sealedAt: z.ZodOptional<z.ZodNumber>;
53
+ sequence: z.ZodOptional<z.ZodNumber>;
54
+ }, z.core.$strict>>;
55
+ sourceMutationIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
56
  writeOptions: z.ZodOptional<z.ZodObject<{
49
57
  readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
50
58
  onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
@@ -76,6 +84,7 @@ export declare function deserializePersistedTransaction(row: unknown): Transacti
76
84
  * queue is restored on reconnect.
77
85
  */
78
86
  export declare const persistedMutationSchema: z.ZodObject<{
87
+ mutationId: z.ZodOptional<z.ZodString>;
79
88
  type: z.ZodEnum<{
80
89
  update: "update";
81
90
  create: "create";
@@ -85,6 +94,7 @@ export declare const persistedMutationSchema: z.ZodObject<{
85
94
  modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
86
95
  modelName: z.ZodString;
87
96
  timestamp: z.ZodString;
97
+ capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
88
98
  writeOptions: z.ZodOptional<z.ZodObject<{
89
99
  readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
90
100
  onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
@@ -97,3 +107,76 @@ export declare const persistedMutationSchema: z.ZodObject<{
97
107
  }, z.core.$loose>>;
98
108
  }, z.core.$loose>;
99
109
  export type PersistedQueuedMutation = z.infer<typeof persistedMutationSchema>;
110
+ export declare const PENDING_MUTATION_RECORD_PREFIX = "pending-mutation:";
111
+ /**
112
+ * Stay one hour inside the server's 24-hour idempotency retention window.
113
+ * A journaled write older than this can no longer be deduplicated by the
114
+ * server, so restore holds it for review instead of replaying it.
115
+ */
116
+ export declare const PENDING_MUTATION_REPLAY_WINDOW_MS: number;
117
+ /** Scope-less records written by the first aggregate-journal release. */
118
+ export declare const legacyPendingMutationRecordSchema: z.ZodObject<{
119
+ storageVersion: z.ZodLiteral<1>;
120
+ id: z.ZodString;
121
+ type: z.ZodLiteral<"pending_mutation">;
122
+ mutation: z.ZodObject<{
123
+ type: z.ZodEnum<{
124
+ update: "update";
125
+ create: "create";
126
+ delete: "delete";
127
+ archive: "archive";
128
+ }>;
129
+ modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
130
+ modelName: z.ZodString;
131
+ timestamp: z.ZodString;
132
+ capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
133
+ writeOptions: z.ZodOptional<z.ZodObject<{
134
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
135
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
136
+ reject: "reject";
137
+ overwrite: "overwrite";
138
+ notify: "notify";
139
+ }>>>;
140
+ idempotencyKey: z.ZodOptional<z.ZodString>;
141
+ label: z.ZodOptional<z.ZodString>;
142
+ }, z.core.$loose>>;
143
+ mutationId: z.ZodString;
144
+ }, z.core.$loose>;
145
+ timestamp: z.ZodNumber;
146
+ }, z.core.$strict>;
147
+ export declare const pendingMutationRecordSchema: z.ZodObject<{
148
+ storageVersion: z.ZodLiteral<2>;
149
+ scope: z.ZodObject<{
150
+ organizationId: z.ZodString;
151
+ participantId: z.ZodString;
152
+ namespace: z.ZodString;
153
+ }, z.core.$strict>;
154
+ id: z.ZodString;
155
+ type: z.ZodLiteral<"pending_mutation">;
156
+ mutation: z.ZodObject<{
157
+ type: z.ZodEnum<{
158
+ update: "update";
159
+ create: "create";
160
+ delete: "delete";
161
+ archive: "archive";
162
+ }>;
163
+ modelData: z.ZodRecord<z.ZodString, z.ZodUnknown>;
164
+ modelName: z.ZodString;
165
+ timestamp: z.ZodString;
166
+ capturedChanges: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
167
+ writeOptions: z.ZodOptional<z.ZodObject<{
168
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
169
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
170
+ reject: "reject";
171
+ overwrite: "overwrite";
172
+ notify: "notify";
173
+ }>>>;
174
+ idempotencyKey: z.ZodOptional<z.ZodString>;
175
+ label: z.ZodOptional<z.ZodString>;
176
+ }, z.core.$loose>>;
177
+ mutationId: z.ZodString;
178
+ }, z.core.$loose>;
179
+ timestamp: z.ZodNumber;
180
+ }, z.core.$strict>;
181
+ export type PendingMutationRecord = z.infer<typeof pendingMutationRecordSchema>;
182
+ export declare function pendingMutationRecordId(mutationId: string): string;
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { z } from 'zod';
18
18
  import { computePriorityScore, normalizeModelKey } from './commitPayload.js';
19
+ import { commitEnvelopeMemberSchema, commitOutboxScopeSchema, } from './commitEnvelope.js';
19
20
  /** The subset of a write's options that is stored with each transaction or queued mutation. */
20
21
  const persistedWriteOptionsSchema = z
21
22
  .object({
@@ -49,12 +50,20 @@ export const persistedTransactionSchema = z
49
50
  }),
50
51
  createdAt: z.number().optional(),
51
52
  batchId: z.string().optional(),
53
+ commitEnvelope: commitEnvelopeMemberSchema.optional(),
54
+ sourceMutationIds: z.array(z.string().min(1)).optional(),
52
55
  writeOptions: persistedWriteOptionsSchema.optional(),
53
56
  localOnly: z.boolean().optional(),
54
57
  })
55
58
  .loose();
56
59
  /** The `type` values of stored rows that belong to other parts of the client and are not replayable transactions. */
57
- const NON_REPLAYABLE_TYPES = new Set(['queue', 'awaiting_delta']);
60
+ const NON_REPLAYABLE_TYPES = new Set([
61
+ 'queue',
62
+ 'awaiting_delta',
63
+ 'pending_mutation',
64
+ 'commit_envelope',
65
+ 'http_commit_envelope',
66
+ ]);
58
67
  /**
59
68
  * Reports whether a stored row is one of the non-transaction kinds, so callers
60
69
  * skip it instead of treating it as a corrupt transaction.
@@ -91,6 +100,12 @@ export function deserializePersistedTransaction(row) {
91
100
  priority: 'normal',
92
101
  priorityScore: computePriorityScore(tx.type, tx.modelName),
93
102
  ...(tx.batchId !== undefined ? { batchId: tx.batchId } : {}),
103
+ ...(tx.commitEnvelope !== undefined
104
+ ? { commitEnvelope: tx.commitEnvelope }
105
+ : {}),
106
+ ...(tx.sourceMutationIds !== undefined
107
+ ? { sourceMutationIds: tx.sourceMutationIds }
108
+ : {}),
94
109
  ...(tx.writeOptions !== undefined ? { writeOptions: tx.writeOptions } : {}),
95
110
  ...(tx.localOnly !== undefined ? { localOnly: tx.localOnly } : {}),
96
111
  };
@@ -102,10 +117,40 @@ export function deserializePersistedTransaction(row) {
102
117
  */
103
118
  export const persistedMutationSchema = z
104
119
  .object({
120
+ mutationId: z.string().min(1).optional(),
105
121
  type: z.enum(['create', 'update', 'delete', 'archive']),
106
122
  modelData: z.record(z.string(), z.unknown()),
107
123
  modelName: z.string().min(1),
108
124
  timestamp: z.string(),
125
+ capturedChanges: z.record(z.string(), z.unknown()).optional(),
109
126
  writeOptions: persistedWriteOptionsSchema.optional(),
110
127
  })
111
128
  .loose();
129
+ export const PENDING_MUTATION_RECORD_PREFIX = 'pending-mutation:';
130
+ /**
131
+ * Stay one hour inside the server's 24-hour idempotency retention window.
132
+ * A journaled write older than this can no longer be deduplicated by the
133
+ * server, so restore holds it for review instead of replaying it.
134
+ */
135
+ export const PENDING_MUTATION_REPLAY_WINDOW_MS = 23 * 60 * 60 * 1000;
136
+ const pendingMutationRecordBaseShape = {
137
+ id: z.string().startsWith(PENDING_MUTATION_RECORD_PREFIX),
138
+ type: z.literal('pending_mutation'),
139
+ mutation: persistedMutationSchema.extend({
140
+ mutationId: z.string().min(1),
141
+ }),
142
+ timestamp: z.number().int().nonnegative(),
143
+ };
144
+ /** Scope-less records written by the first aggregate-journal release. */
145
+ export const legacyPendingMutationRecordSchema = z.strictObject({
146
+ ...pendingMutationRecordBaseShape,
147
+ storageVersion: z.literal(1),
148
+ });
149
+ export const pendingMutationRecordSchema = z.strictObject({
150
+ ...pendingMutationRecordBaseShape,
151
+ storageVersion: z.literal(2),
152
+ scope: commitOutboxScopeSchema,
153
+ });
154
+ export function pendingMutationRecordId(mutationId) {
155
+ return `${PENDING_MUTATION_RECORD_PREFIX}${mutationId}`;
156
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ /** Machine-readable reasons a live delta stream must resume via catch-up. */
3
+ export declare const bootstrapReasonSchema: z.ZodEnum<{
4
+ too_far_behind: "too_far_behind";
5
+ too_many_deltas: "too_many_deltas";
6
+ missing_entities: "missing_entities";
7
+ stream_gap: "stream_gap";
8
+ }>;
9
+ export type BootstrapReason = z.infer<typeof bootstrapReasonSchema>;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ /** Machine-readable reasons a live delta stream must resume via catch-up. */
3
+ export const bootstrapReasonSchema = z.enum([
4
+ 'too_far_behind',
5
+ 'too_many_deltas',
6
+ 'missing_entities',
7
+ 'stream_gap',
8
+ ]);
@@ -222,3 +222,239 @@ export interface MutationResultMessage {
222
222
  };
223
223
  };
224
224
  }
225
+ /**
226
+ * A transport frame carrying the Zod-first v2 commit request. The payload's
227
+ * `schemaVersion` distinguishes it from the legacy `commit` payload during the
228
+ * migration window; the transport does not duplicate the commit contract.
229
+ */
230
+ export declare const commitRequestMessageSchema: z.ZodObject<{
231
+ type: z.ZodLiteral<"commit">;
232
+ payload: z.ZodObject<{
233
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
234
+ schemaVersion: z.ZodLiteral<2>;
235
+ operations: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
236
+ type: z.ZodLiteral<"create">;
237
+ value: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
238
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
239
+ target: z.ZodObject<{
240
+ model: z.ZodString;
241
+ id: z.ZodString;
242
+ }, z.core.$strict>;
243
+ preconditions: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
244
+ type: z.ZodLiteral<"unchanged_since">;
245
+ cursor: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
246
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
247
+ }, z.core.$strict>, z.ZodObject<{
248
+ type: z.ZodLiteral<"version_matches">;
249
+ version: z.ZodNumber;
250
+ }, z.core.$strict>, z.ZodObject<{
251
+ type: z.ZodLiteral<"claim_fence">;
252
+ claimId: z.core.$ZodBranded<z.ZodString, "ClaimId", "out">;
253
+ generation: z.ZodNumber;
254
+ }, z.core.$strict>], "type">>>;
255
+ }, z.core.$strict>, z.ZodObject<{
256
+ type: z.ZodLiteral<"patch">;
257
+ changes: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
258
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
259
+ target: z.ZodObject<{
260
+ model: z.ZodString;
261
+ id: z.ZodString;
262
+ }, z.core.$strict>;
263
+ preconditions: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
264
+ type: z.ZodLiteral<"unchanged_since">;
265
+ cursor: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
266
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
267
+ }, z.core.$strict>, z.ZodObject<{
268
+ type: z.ZodLiteral<"version_matches">;
269
+ version: z.ZodNumber;
270
+ }, z.core.$strict>, z.ZodObject<{
271
+ type: z.ZodLiteral<"claim_fence">;
272
+ claimId: z.core.$ZodBranded<z.ZodString, "ClaimId", "out">;
273
+ generation: z.ZodNumber;
274
+ }, z.core.$strict>], "type">>>;
275
+ }, z.core.$strict>, z.ZodObject<{
276
+ type: z.ZodLiteral<"delete">;
277
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
278
+ target: z.ZodObject<{
279
+ model: z.ZodString;
280
+ id: z.ZodString;
281
+ }, z.core.$strict>;
282
+ preconditions: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
283
+ type: z.ZodLiteral<"unchanged_since">;
284
+ cursor: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
285
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
286
+ }, z.core.$strict>, z.ZodObject<{
287
+ type: z.ZodLiteral<"version_matches">;
288
+ version: z.ZodNumber;
289
+ }, z.core.$strict>, z.ZodObject<{
290
+ type: z.ZodLiteral<"claim_fence">;
291
+ claimId: z.core.$ZodBranded<z.ZodString, "ClaimId", "out">;
292
+ generation: z.ZodNumber;
293
+ }, z.core.$strict>], "type">>>;
294
+ }, z.core.$strict>, z.ZodObject<{
295
+ type: z.ZodLiteral<"archive">;
296
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
297
+ target: z.ZodObject<{
298
+ model: z.ZodString;
299
+ id: z.ZodString;
300
+ }, z.core.$strict>;
301
+ preconditions: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
302
+ type: z.ZodLiteral<"unchanged_since">;
303
+ cursor: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
304
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
305
+ }, z.core.$strict>, z.ZodObject<{
306
+ type: z.ZodLiteral<"version_matches">;
307
+ version: z.ZodNumber;
308
+ }, z.core.$strict>, z.ZodObject<{
309
+ type: z.ZodLiteral<"claim_fence">;
310
+ claimId: z.core.$ZodBranded<z.ZodString, "ClaimId", "out">;
311
+ generation: z.ZodNumber;
312
+ }, z.core.$strict>], "type">>>;
313
+ }, z.core.$strict>, z.ZodObject<{
314
+ type: z.ZodLiteral<"unarchive">;
315
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
316
+ target: z.ZodObject<{
317
+ model: z.ZodString;
318
+ id: z.ZodString;
319
+ }, z.core.$strict>;
320
+ preconditions: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
321
+ type: z.ZodLiteral<"unchanged_since">;
322
+ cursor: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
323
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
324
+ }, z.core.$strict>, z.ZodObject<{
325
+ type: z.ZodLiteral<"version_matches">;
326
+ version: z.ZodNumber;
327
+ }, z.core.$strict>, z.ZodObject<{
328
+ type: z.ZodLiteral<"claim_fence">;
329
+ claimId: z.core.$ZodBranded<z.ZodString, "ClaimId", "out">;
330
+ generation: z.ZodNumber;
331
+ }, z.core.$strict>], "type">>>;
332
+ }, z.core.$strict>], "type">>;
333
+ metadata: z.ZodOptional<z.ZodObject<{
334
+ label: z.ZodOptional<z.ZodString>;
335
+ causedByTaskId: z.ZodOptional<z.ZodString>;
336
+ }, z.core.$strict>>;
337
+ }, z.core.$strict>;
338
+ }, z.core.$strict>;
339
+ export type CommitRequestMessage = z.output<typeof commitRequestMessageSchema>;
340
+ /** A transport frame carrying one terminal, status-discriminated v2 receipt. */
341
+ export declare const commitResultMessageSchema: z.ZodObject<{
342
+ type: z.ZodLiteral<"commit_result">;
343
+ payload: z.ZodDiscriminatedUnion<[z.ZodObject<{
344
+ status: z.ZodLiteral<"committed">;
345
+ syncCursor: z.core.$ZodBranded<z.ZodString, "SyncCursor", "out">;
346
+ operationCount: z.ZodNumber;
347
+ object: z.ZodLiteral<"commit_receipt">;
348
+ schemaVersion: z.ZodLiteral<2>;
349
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
350
+ commitId: z.core.$ZodBranded<z.ZodUUID, "CommitId", "out">;
351
+ }, z.core.$strict>, z.ZodObject<{
352
+ status: z.ZodLiteral<"conflicted">;
353
+ syncCursor: z.core.$ZodBranded<z.ZodString, "SyncCursor", "out">;
354
+ conflicts: z.ZodArray<z.ZodObject<{
355
+ operationId: z.core.$ZodBranded<z.ZodString, "OperationId", "out">;
356
+ target: z.ZodObject<{
357
+ model: z.ZodString;
358
+ id: z.ZodString;
359
+ }, z.core.$strict>;
360
+ fields: z.ZodArray<z.ZodString>;
361
+ currentValues: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
362
+ observedAt: z.core.$ZodBranded<z.ZodString, "SyncOffset", "out">;
363
+ }, z.core.$strict>>;
364
+ object: z.ZodLiteral<"commit_receipt">;
365
+ schemaVersion: z.ZodLiteral<2>;
366
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
367
+ commitId: z.core.$ZodBranded<z.ZodUUID, "CommitId", "out">;
368
+ }, z.core.$strict>, z.ZodObject<{
369
+ status: z.ZodLiteral<"rejected">;
370
+ error: z.ZodObject<{
371
+ code: z.ZodUnion<readonly [z.ZodEnum<{}>, z.ZodCustom<`policy:${string}`, `policy:${string}`>]>;
372
+ message: z.ZodString;
373
+ field: z.ZodOptional<z.ZodString>;
374
+ requiredCapability: z.ZodOptional<z.ZodObject<{
375
+ scope: z.ZodString;
376
+ constraints: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>>;
377
+ issuer: z.ZodOptional<z.ZodString>;
378
+ ttlSeconds: z.ZodOptional<z.ZodNumber>;
379
+ nonce: z.ZodOptional<z.ZodString>;
380
+ }, z.core.$strict>>;
381
+ details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
382
+ }, z.core.$strict>;
383
+ object: z.ZodLiteral<"commit_receipt">;
384
+ schemaVersion: z.ZodLiteral<2>;
385
+ idempotencyKey: z.core.$ZodBranded<z.ZodString, "IdempotencyKey", "out">;
386
+ commitId: z.core.$ZodBranded<z.ZodUUID, "CommitId", "out">;
387
+ }, z.core.$strict>], "status">;
388
+ }, z.core.$strict>;
389
+ export type CommitResultMessage = z.output<typeof commitResultMessageSchema>;
390
+ /** Explicit compatibility name for the v1 uppercase operation schema. */
391
+ export declare const legacyCommitOperationSchema: z.ZodObject<{
392
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
393
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
394
+ reject: "reject";
395
+ overwrite: "overwrite";
396
+ notify: "notify";
397
+ }>>>;
398
+ type: z.ZodEnum<{
399
+ CREATE: "CREATE";
400
+ UPDATE: "UPDATE";
401
+ DELETE: "DELETE";
402
+ ARCHIVE: "ARCHIVE";
403
+ UNARCHIVE: "UNARCHIVE";
404
+ }>;
405
+ model: z.ZodString;
406
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
407
+ input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
408
+ transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
409
+ bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
410
+ }, z.core.$strip>;
411
+ /** Explicit compatibility name for the v1 `clientTxId` payload schema. */
412
+ export declare const legacyCommitPayloadSchema: z.ZodObject<{
413
+ operations: z.ZodArray<z.ZodObject<{
414
+ readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
415
+ onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
416
+ reject: "reject";
417
+ overwrite: "overwrite";
418
+ notify: "notify";
419
+ }>>>;
420
+ type: z.ZodEnum<{
421
+ CREATE: "CREATE";
422
+ UPDATE: "UPDATE";
423
+ DELETE: "DELETE";
424
+ ARCHIVE: "ARCHIVE";
425
+ UNARCHIVE: "UNARCHIVE";
426
+ }>;
427
+ model: z.ZodString;
428
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
429
+ input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
430
+ transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
431
+ bypass: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
432
+ }, z.core.$strip>>;
433
+ clientTxId: z.ZodString;
434
+ causedByTaskId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
435
+ reads: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
436
+ model: z.ZodString;
437
+ id: z.ZodString;
438
+ readAt: z.ZodNumber;
439
+ fields: z.ZodOptional<z.ZodArray<z.ZodString>>;
440
+ onStale: z.ZodOptional<z.ZodEnum<{
441
+ reject: "reject";
442
+ overwrite: "overwrite";
443
+ notify: "notify";
444
+ }>>;
445
+ }, z.core.$strip>, z.ZodObject<{
446
+ group: z.ZodString;
447
+ readAt: z.ZodNumber;
448
+ onStale: z.ZodOptional<z.ZodEnum<{
449
+ reject: "reject";
450
+ overwrite: "overwrite";
451
+ notify: "notify";
452
+ }>>;
453
+ }, z.core.$strip>]>>>>;
454
+ }, z.core.$strip>;
455
+ /** Explicit compatibility alias for the v1 uppercase operation shape. */
456
+ export type LegacyCommitOperation = CommitOperation;
457
+ /** Explicit compatibility alias for the v1 `clientTxId` commit frame. */
458
+ export type LegacyCommitMessage = CommitMessage;
459
+ /** Explicit compatibility alias for the v1 boolean-style mutation receipt. */
460
+ export type LegacyMutationResultMessage = MutationResultMessage;
@@ -16,6 +16,7 @@ import { z } from 'zod';
16
16
  // The runtime schema primitives are imported straight from the coordination
17
17
  // schema module to keep this file's runtime dependencies limited to Zod.
18
18
  import { commitOperationSchema as coordinationCommitOperationSchema, readDependencySchema, } from '../coordination/schema.js';
19
+ import { commitReceiptSchema as commitReceiptV2Schema, commitRequestSchema as commitRequestV2Schema, } from '../commit/contract.js';
19
20
  /**
20
21
  * Runtime validator for {@link CommitOperation}. Both commit transports — the
21
22
  * WebSocket `commit` frame and the HTTP `/v1/commits` endpoint — run this check
@@ -46,3 +47,23 @@ export const commitPayloadSchema = z.object({
46
47
  // Pins the schema to the payload type: fails to compile if either side drifts.
47
48
  const _commitPayloadContract = true;
48
49
  void _commitPayloadContract;
50
+ // ── Commit contract v2 ───────────────────────────────────────────────────
51
+ /**
52
+ * A transport frame carrying the Zod-first v2 commit request. The payload's
53
+ * `schemaVersion` distinguishes it from the legacy `commit` payload during the
54
+ * migration window; the transport does not duplicate the commit contract.
55
+ */
56
+ export const commitRequestMessageSchema = z.strictObject({
57
+ type: z.literal('commit'),
58
+ payload: commitRequestV2Schema,
59
+ });
60
+ /** A transport frame carrying one terminal, status-discriminated v2 receipt. */
61
+ export const commitResultMessageSchema = z.strictObject({
62
+ type: z.literal('commit_result'),
63
+ payload: commitReceiptV2Schema,
64
+ });
65
+ // ── Explicit v1 compatibility names ─────────────────────────────────────
66
+ /** Explicit compatibility name for the v1 uppercase operation schema. */
67
+ export const legacyCommitOperationSchema = commitOperationSchema;
68
+ /** Explicit compatibility name for the v1 `clientTxId` payload schema. */
69
+ export const legacyCommitPayloadSchema = commitPayloadSchema;
@@ -19,9 +19,11 @@ export { errorEnvelope, statusForType } from './errorEnvelope.js';
19
19
  export type { ErrorEnvelope } from './errorEnvelope.js';
20
20
  export { listEnvelope } from './listEnvelope.js';
21
21
  export type { ListEnvelope } from './listEnvelope.js';
22
- export { commitOperationSchema, commitPayloadSchema } from './frames.js';
22
+ export { bootstrapReasonSchema } from './bootstrapReason.js';
23
+ export type { BootstrapReason } from './bootstrapReason.js';
24
+ export { commitOperationSchema, commitPayloadSchema, commitRequestMessageSchema, commitResultMessageSchema, legacyCommitOperationSchema, legacyCommitPayloadSchema, } from './frames.js';
23
25
  export { PROTOCOL_VERSION, MIN_SUPPORTED_PROTOCOL_VERSION, WS_CLOSE_PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER, protocolVersionProblem, } from './protocolVersion.js';
24
- export type { CommitOperation, MutationMessage, CommitMessage, MutationResultMessage, } from './frames.js';
26
+ export type { CommitOperation, MutationMessage, CommitMessage, MutationResultMessage, CommitRequestMessage, CommitResultMessage, LegacyCommitOperation, LegacyCommitMessage, LegacyMutationResultMessage, } from './frames.js';
25
27
  export { participantKindSchema, confirmationStateSchema, syncDeltaActionSchema, wireDeltaDataSchema, participantRefSchema, syncDeltaWireCoreSchema, clientSyncDeltaSchema, serverSyncDeltaSchema, } from './delta.js';
26
28
  export type { ParticipantKind, ConfirmationState, SyncDeltaAction, WireDeltaData, ParticipantRef, SyncDeltaWireCore, ClientSyncDelta, ServerSyncDelta, } from './delta.js';
27
29
  export { AbloError, AbloAuthenticationError, AbloPermissionError, AbloValidationError, AbloRateLimitError, AbloIdempotencyError, AbloConnectionError, AbloServerError, AbloStaleContextError, AbloClaimedError, CapabilityError, SyncSessionError, docUrlForCode, translateHttpError, errorFromWire, toAbloError, ERROR_CONTRACT_VERSION, errorCodeSpec, } from '../errors.js';
@@ -17,11 +17,12 @@
17
17
  */
18
18
  export { errorEnvelope, statusForType } from './errorEnvelope.js';
19
19
  export { listEnvelope } from './listEnvelope.js';
20
+ export { bootstrapReasonSchema } from './bootstrapReason.js';
20
21
  // The write-path frame contract: the message shapes shared by the client and
21
22
  // the server. The runtime Zod validators sit beside the interfaces and are
22
23
  // pinned to them, and they gate every operation and payload on both commit
23
24
  // transports.
24
- export { commitOperationSchema, commitPayloadSchema } from './frames.js';
25
+ export { commitOperationSchema, commitPayloadSchema, commitRequestMessageSchema, commitResultMessageSchema, legacyCommitOperationSchema, legacyCommitPayloadSchema, } from './frames.js';
25
26
  // Protocol versioning: the single integer the client and server compare to
26
27
  // confirm they can speak to each other, plus the WebSocket close code used to
27
28
  // reject a mismatch. See protocolVersion.ts for the changelog and deploy rules.
package/docs/api.md CHANGED
@@ -206,27 +206,27 @@ The SDK is a convenience wrapper over a model-scoped HTTP surface — the same
206
206
  noun (`model`) and verbs as `ablo.<model>.…`. Non-JS callers (or curl) use it
207
207
  directly. The table below shows the shape with `{model}` as a placeholder; the
208
208
  [OpenAPI spec](./openapi.json) expands it into one **typed** path per model
209
- (`/v1/models/task`, `/v1/models/deck`, …, generated from your schema) so each
209
+ (`/api/v1/models/task`, `/api/v1/models/deck`, …, generated from your schema) so each
210
210
  endpoint documents that model's real field contract instead of a generic blob.
211
211
 
212
212
  | SDK call | HTTP |
213
213
  |---|---|
214
- | `ablo.<model>.create({ data })` | `POST /v1/models/{model}` |
215
- | `ablo.<model>.list({ where })` | `GET /v1/models/{model}` |
216
- | `ablo.<model>.retrieve({ id })` | `GET /v1/models/{model}/{id}` |
217
- | `ablo.<model>.update({ id, data })` | `PATCH /v1/models/{model}/{id}` |
218
- | `ablo.<model>.delete({ id })` | `DELETE /v1/models/{model}/{id}` |
219
- | `ablo.<model>.claim({ id })` | `POST /v1/models/{model}/{id}/claim` |
220
- | (release a claim) | `DELETE /v1/models/{model}/{id}/claim` |
214
+ | `ablo.<model>.create({ data })` | `POST /api/v1/models/{model}` |
215
+ | `ablo.<model>.list({ where })` | `GET /api/v1/models/{model}` |
216
+ | `ablo.<model>.retrieve({ id })` | `GET /api/v1/models/{model}/{id}` |
217
+ | `ablo.<model>.update({ id, data })` | `PATCH /api/v1/models/{model}/{id}` |
218
+ | `ablo.<model>.delete({ id })` | `DELETE /api/v1/models/{model}/{id}` |
219
+ | `ablo.<model>.claim({ id })` | `POST /api/v1/models/{model}/{id}/claim` |
220
+ | (release a claim) | `DELETE /api/v1/models/{model}/{id}/claim` |
221
221
 
222
222
  Auth is a bearer API key: `Authorization: Bearer sk_…`. Mutations take an
223
223
  `Idempotency-Key` header — derive it from the business event, not a random
224
224
  value, so a retry never double-writes. Writes return a `CommitReceipt`; a
225
225
  rejected write carries an error `code` (e.g. `stale_context`, `intent_conflict`)
226
- to act on. `GET /v1/models/{model}` is cursor-paginated (`limit`, `order`,
226
+ to act on. `GET /api/v1/models/{model}` is cursor-paginated (`limit`, `order`,
227
227
  `order_by`, `starting_after`) and returns `{ data, has_more, next_cursor }`.
228
228
 
229
- `POST /v1/commits` remains the path for **atomic multi-op** writes (several
229
+ `POST /api/v1/commits` remains the path for **atomic multi-op** writes (several
230
230
  operations across rows/models that must commit together) — the per-model routes
231
231
  above are the one-record path. Both run the identical guarded-write engine.
232
232
 
@@ -465,13 +465,13 @@ Each beat's answer carries two more things:
465
465
 
466
466
  Works identically on both transports: the realtime client sends a
467
467
  `claim_heartbeat` frame; the HTTP client posts
468
- `POST /v1/models/{model}/{id}/claim/heartbeat` (`{ ttl?, claimId?, details? }`).
468
+ `POST /api/v1/models/{model}/{id}/claim/heartbeat` (`{ ttl?, claimId?, details? }`).
469
469
  Over HTTP, a **queued** claim can heartbeat too — it refreshes the waiter's
470
470
  slot in the line (a queued slot is TTL'd like a lease) and reports
471
471
  `{ status: 'queued', position }`.
472
472
 
473
473
  A stateless worker holding **many** rows beats them all in one round trip:
474
- `ablo.claims.heartbeatAll({ ttl: '5m' })` → `POST /v1/claims/heartbeat`, one
474
+ `ablo.claims.heartbeatAll({ ttl: '5m' })` → `POST /api/v1/claims/heartbeat`, one
475
475
  entry per extended lease. This is the socketless twin of the realtime
476
476
  keepalive, which already renews every held lease on each ping.
477
477
 
package/docs/mcp.md CHANGED
@@ -16,7 +16,7 @@ edits rows → coordination; teaching your IDE assistant the SDK → helper.
16
16
  ## Coordination server (`@ablo/mcp`)
17
17
 
18
18
  The coordination server is the MCP projection of the model-scoped API
19
- (`/v1/models/...`) — the same surface as `ablo.<model>.create/update/claim` and
19
+ (`/api/v1/models/...`) — the same surface as `ablo.<model>.create/update/claim` and
20
20
  the REST routes, rendered as tools. An agent connects with your API key and
21
21
  gets one safe loop: **claim → read → commit → release.**
22
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "The Collaboration Layer For AI Agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -28,6 +28,11 @@
28
28
  "import": "./dist/coordination/index.js",
29
29
  "default": "./dist/coordination/index.js"
30
30
  },
31
+ "./commit": {
32
+ "types": "./dist/commit/index.d.ts",
33
+ "import": "./dist/commit/index.js",
34
+ "default": "./dist/commit/index.js"
35
+ },
31
36
  "./react": {
32
37
  "types": "./dist/react/index.d.ts",
33
38
  "import": "./dist/react/index.js",
@@ -207,7 +212,7 @@
207
212
  "mobx": "^6.13.7",
208
213
  "mobx-react-lite": "^4.0.0",
209
214
  "uuid": "^11.1.0",
210
- "zod": "^4.0.17"
215
+ "zod": "^4.1.11"
211
216
  },
212
217
  "devDependencies": {
213
218
  "@ai-sdk/provider": "^3.0.0",