@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +3 -2
  3. package/dist/BaseSyncedStore.js +3 -1
  4. package/dist/Database.js +29 -2
  5. package/dist/SyncClient.d.ts +5 -3
  6. package/dist/SyncClient.js +8 -4
  7. package/dist/ai-sdk/coordinatedTool.d.ts +0 -1
  8. package/dist/ai-sdk/coordinatedTool.js +0 -1
  9. package/dist/ai-sdk/coordinationContext.js +1 -5
  10. package/dist/cli.cjs +377 -84
  11. package/dist/client/Ablo.d.ts +1 -1
  12. package/dist/client/Ablo.js +24 -36
  13. package/dist/client/auth.d.ts +8 -18
  14. package/dist/client/auth.js +16 -96
  15. package/dist/client/createModelProxy.d.ts +16 -11
  16. package/dist/client/createModelProxy.js +162 -20
  17. package/dist/client/httpClient.d.ts +1 -1
  18. package/dist/client/httpTransport.js +263 -89
  19. package/dist/client/options.d.ts +0 -15
  20. package/dist/client/resourceTypes.d.ts +13 -26
  21. package/dist/client/schemaConfig.js +5 -0
  22. package/dist/client/writeOptionsSchema.d.ts +1 -0
  23. package/dist/client/writeOptionsSchema.js +11 -2
  24. package/dist/client/wsMutationExecutor.d.ts +3 -5
  25. package/dist/coordination/schema.d.ts +17 -13
  26. package/dist/coordination/schema.js +36 -10
  27. package/dist/errorCodes.d.ts +9 -4
  28. package/dist/errorCodes.js +10 -5
  29. package/dist/errors.js +4 -9
  30. package/dist/index.d.ts +4 -0
  31. package/dist/interfaces/index.d.ts +31 -19
  32. package/dist/schema/index.d.ts +1 -1
  33. package/dist/schema/index.js +4 -1
  34. package/dist/schema/schema.d.ts +13 -0
  35. package/dist/schema/select.js +7 -0
  36. package/dist/schema/syncDeltaRow.d.ts +31 -13
  37. package/dist/schema/syncDeltaRow.js +33 -15
  38. package/dist/schema/tenancy.d.ts +52 -16
  39. package/dist/schema/tenancy.js +86 -25
  40. package/dist/server/commit.d.ts +8 -24
  41. package/dist/server/index.d.ts +1 -0
  42. package/dist/server/index.js +1 -0
  43. package/dist/server/readConfig.d.ts +10 -0
  44. package/dist/source/adapter.d.ts +26 -15
  45. package/dist/source/adapter.js +5 -3
  46. package/dist/source/adapters/drizzle.js +35 -9
  47. package/dist/source/adapters/kysely.d.ts +33 -61
  48. package/dist/source/adapters/kysely.js +170 -177
  49. package/dist/source/adapters/kyselyMutationCore.d.ts +76 -0
  50. package/dist/source/adapters/kyselyMutationCore.js +125 -0
  51. package/dist/source/adapters/memory.d.ts +1 -1
  52. package/dist/source/adapters/memory.js +24 -10
  53. package/dist/source/adapters/prisma.js +35 -8
  54. package/dist/source/conformance.d.ts +26 -28
  55. package/dist/source/conformance.js +142 -69
  56. package/dist/source/contract.d.ts +46 -4
  57. package/dist/source/contract.js +66 -5
  58. package/dist/source/factory.d.ts +3 -3
  59. package/dist/source/factory.js +24 -3
  60. package/dist/source/idempotency.d.ts +46 -0
  61. package/dist/source/idempotency.js +129 -0
  62. package/dist/source/index.d.ts +6 -4
  63. package/dist/source/index.js +5 -3
  64. package/dist/source/migrations.d.ts +9 -5
  65. package/dist/source/migrations.js +39 -6
  66. package/dist/source/types.d.ts +88 -14
  67. package/dist/source/types.js +16 -0
  68. package/dist/surface.d.ts +1 -1
  69. package/dist/surface.js +0 -1
  70. package/dist/sync/BootstrapFetcher.js +7 -0
  71. package/dist/sync/SyncWebSocket.d.ts +5 -27
  72. package/dist/sync/awaitClaimGrant.d.ts +6 -0
  73. package/dist/sync/awaitClaimGrant.js +12 -2
  74. package/dist/sync/commitFrames.d.ts +2 -4
  75. package/dist/sync/commitFrames.js +1 -0
  76. package/dist/sync/createClaimStream.d.ts +1 -1
  77. package/dist/sync/createClaimStream.js +13 -15
  78. package/dist/sync/deltaPipeline.d.ts +1 -1
  79. package/dist/sync/deltaPipeline.js +1 -1
  80. package/dist/sync/participants.d.ts +2 -3
  81. package/dist/sync/participants.js +2 -2
  82. package/dist/sync/wsFrameHandlers.js +35 -15
  83. package/dist/testing/mocks/MockMutationExecutor.d.ts +21 -0
  84. package/dist/testing/mocks/MockMutationExecutor.js +53 -1
  85. package/dist/transactions/TransactionQueue.d.ts +61 -9
  86. package/dist/transactions/TransactionQueue.js +502 -114
  87. package/dist/transactions/commitEnvelope.d.ts +4 -0
  88. package/dist/transactions/commitEnvelope.js +25 -0
  89. package/dist/transactions/commitPayload.d.ts +23 -5
  90. package/dist/transactions/commitPayload.js +3 -0
  91. package/dist/transactions/deltaConfirmation.d.ts +2 -1
  92. package/dist/transactions/deltaConfirmation.js +22 -4
  93. package/dist/transactions/durableWriteStore.d.ts +8 -1
  94. package/dist/transactions/httpCommitEnvelope.d.ts +3 -1
  95. package/dist/transactions/httpCommitEnvelope.js +30 -0
  96. package/dist/transactions/replayValidation.d.ts +4 -0
  97. package/dist/transactions/replayValidation.js +6 -0
  98. package/dist/types/streams.d.ts +29 -17
  99. package/dist/wire/commit.d.ts +671 -0
  100. package/dist/wire/commit.js +300 -0
  101. package/dist/wire/delta.d.ts +4 -0
  102. package/dist/wire/delta.js +9 -0
  103. package/dist/wire/frames.d.ts +17 -45
  104. package/dist/wire/index.d.ts +2 -0
  105. package/dist/wire/index.js +4 -0
  106. package/docs/agent-messaging.md +4 -5
  107. package/docs/agents.md +1 -2
  108. package/docs/api-keys.md +1 -2
  109. package/docs/client-behavior.md +4 -6
  110. package/docs/coordination.md +64 -9
  111. package/docs/data-sources.md +4 -7
  112. package/docs/integration-guide.md +6 -8
  113. package/docs/migration.md +0 -3
  114. package/docs/quickstart.md +7 -8
  115. package/docs/schema-contract.md +7 -8
  116. package/package.json +4 -4
  117. package/dist/client/registerDataSource.d.ts +0 -19
  118. package/dist/client/registerDataSource.js +0 -66
@@ -6,6 +6,7 @@
6
6
  * so tests can drive the delta-confirmation flow that depends on those ids.
7
7
  */
8
8
  import type { MutationExecutor, MutationOperation, MutationOptions, CommitResult } from '../../interfaces/index.js';
9
+ import type { StaleNotification } from '../../coordination/schema.js';
9
10
  export interface CapturedMutation {
10
11
  method: string;
11
12
  modelName?: string;
@@ -23,6 +24,14 @@ export interface MockMutationExecutorOptions {
23
24
  shouldSucceed?: boolean;
24
25
  /** A delay applied before each call resolves, in milliseconds, to simulate a slow network. */
25
26
  latencyMs?: number;
27
+ /** Optional explicit receipt status. Omission preserves legacy confirmed semantics. */
28
+ status?: CommitResult['status'];
29
+ /** Optional server-issued source/WAL correlation returned with queued receipts. */
30
+ correlationId?: string;
31
+ /** Optional stale-context notifications returned with each commit. */
32
+ notifications?: StaleNotification[];
33
+ /** Optional zero-row target ids returned with each commit. */
34
+ missingIds?: string[];
26
35
  }
27
36
  export declare class MockMutationExecutor implements MutationExecutor {
28
37
  /** Every captured call, in the order it was made. Assertions read from this list. */
@@ -31,6 +40,10 @@ export declare class MockMutationExecutor implements MutationExecutor {
31
40
  private _syncId;
32
41
  private _shouldSucceed;
33
42
  private _latencyMs;
43
+ private _status;
44
+ private _correlationId;
45
+ private _notifications;
46
+ private _missingIds;
34
47
  /** Per-method failure overrides: method name → error */
35
48
  private _failureOverrides;
36
49
  /** Per-method response overrides */
@@ -40,6 +53,14 @@ export declare class MockMutationExecutor implements MutationExecutor {
40
53
  get currentSyncId(): number;
41
54
  /** Sets the next sync id the executor will return. */
42
55
  setSyncId(id: number): void;
56
+ /** Sets the settlement status returned by subsequent commit calls. */
57
+ setStatus(status: CommitResult['status']): void;
58
+ /** Sets the opaque source/WAL correlation returned by subsequent commits. */
59
+ setCorrelationId(correlationId: string | undefined): void;
60
+ /** Sets stale-context notifications returned by subsequent commit calls. */
61
+ setNotifications(notifications: StaleNotification[] | undefined): void;
62
+ /** Sets zero-row target ids returned by subsequent commit calls. */
63
+ setMissingIds(missingIds: string[] | undefined): void;
43
64
  /** Makes every mutation reject. Pass an error to control what is thrown. */
44
65
  failAll(error?: Error): void;
45
66
  /** Restores the default where mutations succeed, clearing any failure overrides. */
@@ -13,6 +13,10 @@ export class MockMutationExecutor {
13
13
  _syncId;
14
14
  _shouldSucceed;
15
15
  _latencyMs;
16
+ _status;
17
+ _correlationId;
18
+ _notifications;
19
+ _missingIds;
16
20
  /** Per-method failure overrides: method name → error */
17
21
  _failureOverrides = new Map();
18
22
  /** Per-method response overrides */
@@ -21,6 +25,10 @@ export class MockMutationExecutor {
21
25
  this._syncId = options.initialSyncId ?? 1;
22
26
  this._shouldSucceed = options.shouldSucceed ?? true;
23
27
  this._latencyMs = options.latencyMs ?? 0;
28
+ this._status = options.status;
29
+ this._correlationId = options.correlationId;
30
+ this._notifications = options.notifications;
31
+ this._missingIds = options.missingIds;
24
32
  }
25
33
  // ─────────────────────────────────────────────
26
34
  // Test control API
@@ -33,6 +41,22 @@ export class MockMutationExecutor {
33
41
  setSyncId(id) {
34
42
  this._syncId = id;
35
43
  }
44
+ /** Sets the settlement status returned by subsequent commit calls. */
45
+ setStatus(status) {
46
+ this._status = status;
47
+ }
48
+ /** Sets the opaque source/WAL correlation returned by subsequent commits. */
49
+ setCorrelationId(correlationId) {
50
+ this._correlationId = correlationId;
51
+ }
52
+ /** Sets stale-context notifications returned by subsequent commit calls. */
53
+ setNotifications(notifications) {
54
+ this._notifications = notifications;
55
+ }
56
+ /** Sets zero-row target ids returned by subsequent commit calls. */
57
+ setMissingIds(missingIds) {
58
+ this._missingIds = missingIds;
59
+ }
36
60
  /** Makes every mutation reject. Pass an error to control what is thrown. */
37
61
  failAll(error) {
38
62
  this._shouldSucceed = false;
@@ -67,6 +91,10 @@ export class MockMutationExecutor {
67
91
  this._syncId = options?.initialSyncId ?? 1;
68
92
  this._shouldSucceed = options?.shouldSucceed ?? true;
69
93
  this._latencyMs = options?.latencyMs ?? 0;
94
+ this._status = options?.status;
95
+ this._correlationId = options?.correlationId;
96
+ this._notifications = options?.notifications;
97
+ this._missingIds = options?.missingIds;
70
98
  this._failureOverrides.clear();
71
99
  this._responseOverrides.clear();
72
100
  }
@@ -78,7 +106,31 @@ export class MockMutationExecutor {
78
106
  await this._maybeDelay();
79
107
  this._maybeThrow('commit');
80
108
  const syncId = this._syncId++;
81
- return { lastSyncId: syncId };
109
+ if (this._status === 'queued') {
110
+ if (!this._correlationId) {
111
+ throw new Error('Mock queued commits require a correlationId');
112
+ }
113
+ return {
114
+ lastSyncId: 0,
115
+ status: 'queued',
116
+ correlationId: this._correlationId,
117
+ ...(this._notifications ? { notifications: this._notifications } : {}),
118
+ ...(this._missingIds ? { missingIds: this._missingIds } : {}),
119
+ };
120
+ }
121
+ const evidence = {
122
+ ...(this._notifications ? { notifications: this._notifications } : {}),
123
+ ...(this._missingIds ? { missingIds: this._missingIds } : {}),
124
+ };
125
+ if (this._status === 'confirmed') {
126
+ return {
127
+ lastSyncId: syncId,
128
+ status: 'confirmed',
129
+ ...(this._correlationId ? { correlationId: this._correlationId } : {}),
130
+ ...evidence,
131
+ };
132
+ }
133
+ return { lastSyncId: syncId, ...evidence };
82
134
  }
83
135
  async executeCreate(modelName, id, input, clientMutationId) {
84
136
  this._capture('executeCreate', { modelName, modelId: id, input, clientMutationId });
@@ -45,10 +45,12 @@ interface CommitTransaction {
45
45
  causedByTaskId?: string | null;
46
46
  /** Read dependencies for the whole batch, forwarded to the executor so the server can detect stale-context writes. */
47
47
  reads?: ReadDependency[] | null;
48
- status: 'pending' | 'executing' | 'completed' | 'failed';
48
+ status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed';
49
49
  createdAt: number;
50
50
  attempts: number;
51
51
  lastSyncId?: number;
52
+ /** Opaque customer-side batch ledger key returned by a queued receipt. */
53
+ correlationId?: string;
52
54
  error?: Error;
53
55
  sealedAt: number;
54
56
  sequence: number;
@@ -113,6 +115,20 @@ export declare class TransactionQueue extends EventEmitter {
113
115
  private deferredDeletesByCreate;
114
116
  private commitLane;
115
117
  private commitStore;
118
+ /**
119
+ * Small race buffer for authoritative echoes that arrive before the queued
120
+ * mutation receipt. The forward and WAL stream are independent channels, so
121
+ * either can win without changing the settlement result.
122
+ */
123
+ private recentDeltaCorrelations;
124
+ /**
125
+ * Client-facing confirmation deadlines for source-forwarded writes. These
126
+ * timers settle only the caller waiting for `confirmed`; the accepted write
127
+ * remains in `awaiting_delta`, with its durable envelope intact, until the
128
+ * authoritative WAL echo arrives.
129
+ */
130
+ private replicationLagTimeouts;
131
+ private replicationLagErrors;
116
132
  private commitProcessing;
117
133
  private lastCommitSequence;
118
134
  private durableReplayBlock;
@@ -126,6 +142,18 @@ export declare class TransactionQueue extends EventEmitter {
126
142
  private computePriorityScore;
127
143
  private ensureDerivedFields;
128
144
  private entityKey;
145
+ /** Collision-safe receipt target identity across models sharing a row id. */
146
+ private receiptTargetKey;
147
+ /**
148
+ * Relates stale notifications back to write targets without assuming the
149
+ * server's canonical model name uses the same spelling as the public schema
150
+ * key (`Task` versus `tasks`). Exact `(model,id)` wins; a globally unique id
151
+ * is the compatibility fallback. An ambiguous same-id cross-model mismatch
152
+ * is deliberately left unclassified, so it cannot falsely settle a queued
153
+ * write. A notification with no write-target id (or an explicit group) is a
154
+ * declared-read conflict and holds the whole batch.
155
+ */
156
+ private classifyReceiptNotifications;
129
157
  private resolveConfirmation;
130
158
  private takeUnsentCreateForModel;
131
159
  private cancelUnsentCreateForDelete;
@@ -137,6 +165,8 @@ export declare class TransactionQueue extends EventEmitter {
137
165
  private executingCount;
138
166
  private optimisticUpdates;
139
167
  private commitNotifications;
168
+ /** Zero-row targets returned on a successful atomic commit receipt. */
169
+ private commitMissingIds;
140
170
  private readonly deltaConfirmation;
141
171
  private isConnectedFn;
142
172
  private commitOfflineGraceTimer;
@@ -170,8 +200,25 @@ export declare class TransactionQueue extends EventEmitter {
170
200
  * callers never send a separately reconstructed payload after sealing.
171
201
  */
172
202
  private sealDurableCommit;
173
- /** Best-effort cleanup after a definitive result; replay is safe if it fails. */
203
+ /** Best-effort cleanup after a definitive rejection, confirmed ack, or echo. */
174
204
  private removeDurableCommit;
205
+ /**
206
+ * Upgrade a sealed request with permanent connected-source acceptance before
207
+ * exposing the queued receipt. This is not completion: the envelope remains
208
+ * until the matching WAL correlation arrives. The upgrade only makes a
209
+ * crash/restart safe after the hosted 24-hour replay window has elapsed.
210
+ */
211
+ private persistDurableCommitAcceptance;
212
+ /** Parse an untrusted/custom executor receipt without making ambiguity fatal. */
213
+ private parseMutationCommitResult;
214
+ private clearReplicationLagState;
215
+ /**
216
+ * Bounds the public `wait: 'confirmed'` promise without changing the
217
+ * accepted write's lifecycle. A lag timeout is not a rejection from the
218
+ * source database, so it must never emit `transaction:failed`, roll back
219
+ * optimistic state, or remove the durable replay envelope.
220
+ */
221
+ private scheduleReplicationLagTimeout;
175
222
  /**
176
223
  * Takes either one complete retry envelope or a fresh batch. A retry waits
177
224
  * until every original member has re-entered the queue, preventing an
@@ -333,13 +380,15 @@ export declare class TransactionQueue extends EventEmitter {
333
380
  * greatly reduces batch latency.
334
381
  */
335
382
  private processBatch;
383
+ private rememberDeltaCorrelation;
384
+ private queuedCommitEchoSyncId;
385
+ private queuedCommitMatchesCorrelation;
386
+ private completeQueuedCommit;
336
387
  /**
337
- * Confirms every awaiting transaction whose sync-id threshold this delta meets
338
- * or exceeds. The confirmation policy and timeout tracking live in
339
- * {@link DeltaConfirmationTracker} (`./deltaConfirmation.js`).
340
- * @param syncId - The sync id of the received delta.
388
+ * Confirms awaiting writes. Hosted/anomaly receipts keep their sync-id
389
+ * threshold semantics; queued forwards require the exact server correlation.
341
390
  */
342
- onDeltaReceived(syncId: number): void;
391
+ onDeltaReceived(syncId: number, _transactionId?: string, correlationId?: string): void;
343
392
  private scheduleDeltaConfirmationTimeout;
344
393
  /**
345
394
  * Resolves once the given transaction is confirmed and rejects if it fails.
@@ -353,8 +402,10 @@ export declare class TransactionQueue extends EventEmitter {
353
402
  * `ablo.commits.create()` path. The caller supplies the operations; the queue
354
403
  * only retries on reconnect and de-duplicates, and does not apply the change
355
404
  * optimistically or reorder for foreign keys. A duplicate `clientTxId`
356
- * already in flight is ignored: the server's `mutation_log` de-duplicates
357
- * across sessions, and this guard covers a double-enqueue within one session.
405
+ * already in flight is ignored. After an accepted write exceeds its
406
+ * replication-confirmation deadline, an explicit same-key retry reuses the
407
+ * exact sealed envelope as an idempotent status probe; the server's
408
+ * `mutation_log` still prevents a second logical write.
358
409
  */
359
410
  enqueueCommit(clientTxId: string, operations: CommitTransaction['operations'], options?: {
360
411
  causedByTaskId?: string | null;
@@ -377,6 +428,7 @@ export declare class TransactionQueue extends EventEmitter {
377
428
  waitForCommitReceipt(clientTxId: string): Promise<{
378
429
  lastSyncId: number;
379
430
  notifications?: StaleNotification[];
431
+ missingIds?: string[];
380
432
  }>;
381
433
  private isReorderPayload;
382
434
  /**