@abloatai/ablo 0.3.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 (278) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +12 -0
  4. package/README.md +230 -0
  5. package/dist/BaseSyncedStore.d.ts +709 -0
  6. package/dist/BaseSyncedStore.js +1843 -0
  7. package/dist/Database.d.ts +344 -0
  8. package/dist/Database.js +1259 -0
  9. package/dist/LazyReferenceCollection.d.ts +181 -0
  10. package/dist/LazyReferenceCollection.js +460 -0
  11. package/dist/Model.d.ts +339 -0
  12. package/dist/Model.js +715 -0
  13. package/dist/ModelRegistry.d.ts +200 -0
  14. package/dist/ModelRegistry.js +535 -0
  15. package/dist/NetworkMonitor.d.ts +27 -0
  16. package/dist/NetworkMonitor.js +73 -0
  17. package/dist/ObjectPool.d.ts +202 -0
  18. package/dist/ObjectPool.js +1106 -0
  19. package/dist/SyncClient.d.ts +489 -0
  20. package/dist/SyncClient.js +1555 -0
  21. package/dist/SyncEngineContext.d.ts +46 -0
  22. package/dist/SyncEngineContext.js +74 -0
  23. package/dist/adapters/alwaysOnline.d.ts +16 -0
  24. package/dist/adapters/alwaysOnline.js +19 -0
  25. package/dist/adapters/inMemoryStorage.d.ts +30 -0
  26. package/dist/adapters/inMemoryStorage.js +94 -0
  27. package/dist/agent/Agent.d.ts +358 -0
  28. package/dist/agent/Agent.js +500 -0
  29. package/dist/agent/index.d.ts +115 -0
  30. package/dist/agent/index.js +128 -0
  31. package/dist/agent/session.d.ts +90 -0
  32. package/dist/agent/session.js +156 -0
  33. package/dist/agent/types.d.ts +73 -0
  34. package/dist/agent/types.js +10 -0
  35. package/dist/ai-sdk/coordination-context.d.ts +51 -0
  36. package/dist/ai-sdk/coordination-context.js +107 -0
  37. package/dist/ai-sdk/index.d.ts +68 -0
  38. package/dist/ai-sdk/index.js +68 -0
  39. package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
  40. package/dist/ai-sdk/intent-broadcast.js +72 -0
  41. package/dist/ai-sdk/wrap.d.ts +67 -0
  42. package/dist/ai-sdk/wrap.js +45 -0
  43. package/dist/api/index.d.ts +10 -0
  44. package/dist/api/index.js +9 -0
  45. package/dist/auth/index.d.ts +137 -0
  46. package/dist/auth/index.js +246 -0
  47. package/dist/client/Ablo.d.ts +835 -0
  48. package/dist/client/Ablo.js +1440 -0
  49. package/dist/client/ApiClient.d.ts +200 -0
  50. package/dist/client/ApiClient.js +659 -0
  51. package/dist/client/auth.d.ts +79 -0
  52. package/dist/client/auth.js +81 -0
  53. package/dist/client/createInternalComponents.d.ts +44 -0
  54. package/dist/client/createInternalComponents.js +88 -0
  55. package/dist/client/createModelProxy.d.ts +152 -0
  56. package/dist/client/createModelProxy.js +199 -0
  57. package/dist/client/identity.d.ts +63 -0
  58. package/dist/client/identity.js +156 -0
  59. package/dist/client/index.d.ts +36 -0
  60. package/dist/client/index.js +33 -0
  61. package/dist/client/persistence.d.ts +7 -0
  62. package/dist/client/persistence.js +11 -0
  63. package/dist/client/validateAbloOptions.d.ts +42 -0
  64. package/dist/client/validateAbloOptions.js +43 -0
  65. package/dist/config/index.d.ts +10 -0
  66. package/dist/config/index.js +12 -0
  67. package/dist/context.d.ts +27 -0
  68. package/dist/context.js +58 -0
  69. package/dist/core/DatabaseManager.d.ts +108 -0
  70. package/dist/core/DatabaseManager.js +361 -0
  71. package/dist/core/QueryProcessor.d.ts +77 -0
  72. package/dist/core/QueryProcessor.js +262 -0
  73. package/dist/core/QueryView.d.ts +64 -0
  74. package/dist/core/QueryView.js +219 -0
  75. package/dist/core/StoreManager.d.ts +131 -0
  76. package/dist/core/StoreManager.js +334 -0
  77. package/dist/core/ViewRegistry.d.ts +20 -0
  78. package/dist/core/ViewRegistry.js +55 -0
  79. package/dist/core/index.d.ts +34 -0
  80. package/dist/core/index.js +59 -0
  81. package/dist/core/openIDBWithTimeout.d.ts +27 -0
  82. package/dist/core/openIDBWithTimeout.js +63 -0
  83. package/dist/core/query-utils.d.ts +37 -0
  84. package/dist/core/query-utils.js +60 -0
  85. package/dist/errors.d.ts +235 -0
  86. package/dist/errors.js +243 -0
  87. package/dist/index.d.ts +41 -0
  88. package/dist/index.js +82 -0
  89. package/dist/interfaces/headless.d.ts +95 -0
  90. package/dist/interfaces/headless.js +41 -0
  91. package/dist/interfaces/index.d.ts +321 -0
  92. package/dist/interfaces/index.js +8 -0
  93. package/dist/mutators/RecordingTransaction.d.ts +36 -0
  94. package/dist/mutators/RecordingTransaction.js +216 -0
  95. package/dist/mutators/Transaction.d.ts +48 -0
  96. package/dist/mutators/Transaction.js +64 -0
  97. package/dist/mutators/UndoManager.d.ts +114 -0
  98. package/dist/mutators/UndoManager.js +143 -0
  99. package/dist/mutators/defineMutators.d.ts +55 -0
  100. package/dist/mutators/defineMutators.js +28 -0
  101. package/dist/policy/index.d.ts +19 -0
  102. package/dist/policy/index.js +18 -0
  103. package/dist/policy/types.d.ts +74 -0
  104. package/dist/policy/types.js +17 -0
  105. package/dist/principal.d.ts +44 -0
  106. package/dist/principal.js +49 -0
  107. package/dist/query/client.d.ts +43 -0
  108. package/dist/query/client.js +84 -0
  109. package/dist/query/index.d.ts +6 -0
  110. package/dist/query/index.js +5 -0
  111. package/dist/query/types.d.ts +143 -0
  112. package/dist/query/types.js +36 -0
  113. package/dist/react/AbloProvider.d.ts +205 -0
  114. package/dist/react/AbloProvider.js +398 -0
  115. package/dist/react/ClientSideSuspense.d.ts +36 -0
  116. package/dist/react/ClientSideSuspense.js +17 -0
  117. package/dist/react/DefaultFallback.d.ts +24 -0
  118. package/dist/react/DefaultFallback.js +43 -0
  119. package/dist/react/SyncGroupProvider.d.ts +19 -0
  120. package/dist/react/SyncGroupProvider.js +44 -0
  121. package/dist/react/context.d.ts +161 -0
  122. package/dist/react/context.js +35 -0
  123. package/dist/react/index.d.ts +64 -0
  124. package/dist/react/index.js +73 -0
  125. package/dist/react/internalContext.d.ts +35 -0
  126. package/dist/react/internalContext.js +3 -0
  127. package/dist/react/useAblo.d.ts +72 -0
  128. package/dist/react/useAblo.js +63 -0
  129. package/dist/react/useCurrentUserId.d.ts +21 -0
  130. package/dist/react/useCurrentUserId.js +33 -0
  131. package/dist/react/useErrorListener.d.ts +20 -0
  132. package/dist/react/useErrorListener.js +39 -0
  133. package/dist/react/useIntent.d.ts +29 -0
  134. package/dist/react/useIntent.js +42 -0
  135. package/dist/react/useMutate.d.ts +83 -0
  136. package/dist/react/useMutate.js +122 -0
  137. package/dist/react/useMutationFailureListener.d.ts +26 -0
  138. package/dist/react/useMutationFailureListener.js +38 -0
  139. package/dist/react/useMutators.d.ts +56 -0
  140. package/dist/react/useMutators.js +66 -0
  141. package/dist/react/usePresence.d.ts +32 -0
  142. package/dist/react/usePresence.js +41 -0
  143. package/dist/react/useQuery.d.ts +123 -0
  144. package/dist/react/useQuery.js +145 -0
  145. package/dist/react/useReactive.d.ts +35 -0
  146. package/dist/react/useReactive.js +111 -0
  147. package/dist/react/useReader.d.ts +69 -0
  148. package/dist/react/useReader.js +73 -0
  149. package/dist/react/useSyncStatus.d.ts +61 -0
  150. package/dist/react/useSyncStatus.js +76 -0
  151. package/dist/react/useUndoScope.d.ts +36 -0
  152. package/dist/react/useUndoScope.js +73 -0
  153. package/dist/realtime/index.d.ts +10 -0
  154. package/dist/realtime/index.js +9 -0
  155. package/dist/schema/field.d.ts +134 -0
  156. package/dist/schema/field.js +264 -0
  157. package/dist/schema/index.d.ts +29 -0
  158. package/dist/schema/index.js +38 -0
  159. package/dist/schema/model.d.ts +326 -0
  160. package/dist/schema/model.js +89 -0
  161. package/dist/schema/queries.d.ts +203 -0
  162. package/dist/schema/queries.js +145 -0
  163. package/dist/schema/relation.d.ts +172 -0
  164. package/dist/schema/relation.js +104 -0
  165. package/dist/schema/schema.d.ts +259 -0
  166. package/dist/schema/schema.js +188 -0
  167. package/dist/schema/sugar.d.ts +129 -0
  168. package/dist/schema/sugar.js +94 -0
  169. package/dist/source/index.d.ts +423 -0
  170. package/dist/source/index.js +320 -0
  171. package/dist/source/pushQueue.d.ts +112 -0
  172. package/dist/source/pushQueue.js +249 -0
  173. package/dist/stores/ObjectStore.d.ts +103 -0
  174. package/dist/stores/ObjectStore.js +371 -0
  175. package/dist/stores/ObjectStoreContract.d.ts +39 -0
  176. package/dist/stores/ObjectStoreContract.js +1 -0
  177. package/dist/stores/SyncActionStore.d.ts +101 -0
  178. package/dist/stores/SyncActionStore.js +481 -0
  179. package/dist/sync/BootstrapHelper.d.ts +127 -0
  180. package/dist/sync/BootstrapHelper.js +434 -0
  181. package/dist/sync/ConnectionManager.d.ts +136 -0
  182. package/dist/sync/ConnectionManager.js +465 -0
  183. package/dist/sync/HydrationCoordinator.d.ts +137 -0
  184. package/dist/sync/HydrationCoordinator.js +468 -0
  185. package/dist/sync/NetworkProbe.d.ts +43 -0
  186. package/dist/sync/NetworkProbe.js +113 -0
  187. package/dist/sync/OfflineFlush.d.ts +9 -0
  188. package/dist/sync/OfflineFlush.js +22 -0
  189. package/dist/sync/OfflineTransactionStore.d.ts +37 -0
  190. package/dist/sync/OfflineTransactionStore.js +263 -0
  191. package/dist/sync/SyncWebSocket.d.ts +663 -0
  192. package/dist/sync/SyncWebSocket.js +1336 -0
  193. package/dist/sync/createIntentStream.d.ts +33 -0
  194. package/dist/sync/createIntentStream.js +243 -0
  195. package/dist/sync/createPresenceStream.d.ts +46 -0
  196. package/dist/sync/createPresenceStream.js +192 -0
  197. package/dist/sync/createSnapshot.d.ts +33 -0
  198. package/dist/sync/createSnapshot.js +124 -0
  199. package/dist/sync/participants.d.ts +114 -0
  200. package/dist/sync/participants.js +336 -0
  201. package/dist/sync/schemas.d.ts +79 -0
  202. package/dist/sync/schemas.js +78 -0
  203. package/dist/testing/fixtures/bootstrap.d.ts +45 -0
  204. package/dist/testing/fixtures/bootstrap.js +53 -0
  205. package/dist/testing/fixtures/deltas.d.ts +86 -0
  206. package/dist/testing/fixtures/deltas.js +139 -0
  207. package/dist/testing/fixtures/models.d.ts +82 -0
  208. package/dist/testing/fixtures/models.js +270 -0
  209. package/dist/testing/helpers/react-wrapper.d.ts +66 -0
  210. package/dist/testing/helpers/react-wrapper.js +64 -0
  211. package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
  212. package/dist/testing/helpers/sync-engine-harness.js +70 -0
  213. package/dist/testing/helpers/wait.d.ts +25 -0
  214. package/dist/testing/helpers/wait.js +44 -0
  215. package/dist/testing/index.d.ts +21 -0
  216. package/dist/testing/index.js +32 -0
  217. package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
  218. package/dist/testing/mocks/MockMutationExecutor.js +139 -0
  219. package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
  220. package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
  221. package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
  222. package/dist/testing/mocks/MockSyncContext.js +100 -0
  223. package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
  224. package/dist/testing/mocks/MockSyncStore.js +171 -0
  225. package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
  226. package/dist/testing/mocks/MockWebSocket.js +117 -0
  227. package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
  228. package/dist/transactions/OptimisticEchoTracker.js +104 -0
  229. package/dist/transactions/TransactionQueue.d.ts +499 -0
  230. package/dist/transactions/TransactionQueue.js +1895 -0
  231. package/dist/transactions/index.d.ts +16 -0
  232. package/dist/transactions/index.js +7 -0
  233. package/dist/transactions/mutation-error-handler.d.ts +5 -0
  234. package/dist/transactions/mutation-error-handler.js +39 -0
  235. package/dist/types/global.d.ts +107 -0
  236. package/dist/types/global.js +38 -0
  237. package/dist/types/index.d.ts +241 -0
  238. package/dist/types/index.js +70 -0
  239. package/dist/types/streams.d.ts +495 -0
  240. package/dist/types/streams.js +11 -0
  241. package/dist/utils/asyncIterator.d.ts +41 -0
  242. package/dist/utils/asyncIterator.js +142 -0
  243. package/dist/utils/duration.d.ts +28 -0
  244. package/dist/utils/duration.js +47 -0
  245. package/dist/utils/mobx-setup.d.ts +42 -0
  246. package/dist/utils/mobx-setup.js +381 -0
  247. package/docs/api-keys.md +24 -0
  248. package/docs/api.md +230 -0
  249. package/docs/audit.md +81 -0
  250. package/docs/capabilities.md +163 -0
  251. package/docs/client-behavior.md +202 -0
  252. package/docs/data-sources.md +214 -0
  253. package/docs/examples/agent-human.md +84 -0
  254. package/docs/examples/ai-sdk-tool.md +92 -0
  255. package/docs/examples/existing-python-backend.md +249 -0
  256. package/docs/examples/nextjs.md +88 -0
  257. package/docs/examples/server-agent.md +86 -0
  258. package/docs/guarantees.md +148 -0
  259. package/docs/index.md +97 -0
  260. package/docs/integration-guide.md +493 -0
  261. package/docs/interaction-model.md +140 -0
  262. package/docs/mcp/claude-code.md +43 -0
  263. package/docs/mcp/cursor.md +53 -0
  264. package/docs/mcp/windsurf.md +46 -0
  265. package/docs/mcp.md +59 -0
  266. package/docs/quickstart.md +152 -0
  267. package/docs/react.md +115 -0
  268. package/docs/roadmap.md +45 -0
  269. package/examples/README.md +54 -0
  270. package/examples/data-source/README.md +102 -0
  271. package/examples/data-source/ablo-driver.ts +89 -0
  272. package/examples/data-source/customer-server.ts +208 -0
  273. package/examples/data-source/run.ts +101 -0
  274. package/examples/data-source/schema.ts +25 -0
  275. package/examples/quickstart.ts +54 -0
  276. package/examples/tsconfig.json +16 -0
  277. package/llms.txt +143 -0
  278. package/package.json +147 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * OptimisticEchoTracker — receive-layer reconciliation primitive.
3
+ *
4
+ * Tracks the set of transaction ids the client has applied locally
5
+ * but the server has not yet confirmed. When a sync delta arrives
6
+ * carrying a `transactionId` the tracker recognizes, the pool
7
+ * mutation is suppressed (the optimistic write already reflects it).
8
+ * Drained when the matching delta echo lands or when the originating
9
+ * transaction is rolled back.
10
+ *
11
+ * Architectural framing: see
12
+ * `apps/sync-server/docs/OPTIMISTIC_RECONCILIATION.md`. This is the
13
+ * "discriminator at the apply layer" the doc names — without it the
14
+ * authoritative-layer apply path blindly re-applies confirmations on
15
+ * top of whatever optimistic state has since diverged, producing the
16
+ * chart-delete flicker.
17
+ *
18
+ * Bounded by `maxSize` to defend against runaway growth from a
19
+ * pathological "transactions never confirmed and never rolled back"
20
+ * loop. When the bound is hit, the OLDEST id is evicted (FIFO via
21
+ * insertion-ordered Map). Eviction means a future echo of that
22
+ * transaction will be applied as a foreign mutation — no correctness
23
+ * risk for the originating tab (the pool already reflects the local
24
+ * write); the worst case is a single redundant pool re-set.
25
+ */
26
+ const DEFAULT_MAX_SIZE = 10_000;
27
+ export class OptimisticEchoTracker {
28
+ // Map (not Set) for O(1) FIFO eviction via insertion order.
29
+ // Value is unused; Map.keys() iterates in insertion order so
30
+ // `keys().next()` yields the oldest id.
31
+ ids = new Map();
32
+ maxSize;
33
+ // Metrics — internal counters; exposed via `getMetrics()`. Kept
34
+ // numeric (not BigInt) since cumulative-since-page-load fits well
35
+ // under Number.MAX_SAFE_INTEGER for any realistic session.
36
+ _totalAdded = 0;
37
+ _hits = 0;
38
+ _rollbacks = 0;
39
+ _evictions = 0;
40
+ constructor(options = {}) {
41
+ this.maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
42
+ }
43
+ /**
44
+ * Mark a transaction as locally-applied. The next sync delta with a
45
+ * matching `transactionId` will be recognized as the server's
46
+ * confirmation of this same write. Idempotent — repeated calls with
47
+ * the same id are no-ops.
48
+ */
49
+ markPending(transactionId) {
50
+ if (this.ids.has(transactionId))
51
+ return;
52
+ if (this.ids.size >= this.maxSize) {
53
+ const oldest = this.ids.keys().next().value;
54
+ if (oldest !== undefined) {
55
+ this.ids.delete(oldest);
56
+ this._evictions += 1;
57
+ }
58
+ }
59
+ this.ids.set(transactionId, true);
60
+ this._totalAdded += 1;
61
+ }
62
+ /**
63
+ * If the id is currently tracked, drain it and return true (signal
64
+ * to the caller: this is an echo, skip the pool mutation).
65
+ * Otherwise return false (foreign mutation, apply normally).
66
+ *
67
+ * Combined check+drain into one method to make the receive-path
68
+ * idiom hard to misuse: a separate `has` then `drain` would race
69
+ * if multiple deltas with the same id arrived in the same batch.
70
+ */
71
+ consumeEcho(transactionId) {
72
+ if (!transactionId)
73
+ return false;
74
+ if (!this.ids.has(transactionId))
75
+ return false;
76
+ this.ids.delete(transactionId);
77
+ this._hits += 1;
78
+ return true;
79
+ }
80
+ /**
81
+ * Drain on rollback. The transaction was cancelled before a server
82
+ * confirmation arrived — no echo will ever come, so the pending
83
+ * entry would otherwise leak. Counts as a separate metric category
84
+ * so a spike of `rollbacks` (vs `hits`) signals network or
85
+ * server-side health issues.
86
+ */
87
+ drainOnRollback(transactionId) {
88
+ if (this.ids.delete(transactionId)) {
89
+ this._rollbacks += 1;
90
+ }
91
+ }
92
+ getMetrics() {
93
+ return {
94
+ size: this.ids.size,
95
+ totalAdded: this._totalAdded,
96
+ hits: this._hits,
97
+ rollbacks: this._rollbacks,
98
+ evictions: this._evictions,
99
+ };
100
+ }
101
+ clear() {
102
+ this.ids.clear();
103
+ }
104
+ }
@@ -0,0 +1,499 @@
1
+ /**
2
+ * TransactionQueue - Production-ready transaction management
3
+ *
4
+ * Key features:
5
+ * - Optimistic updates with rollback
6
+ * - Conflict resolution strategies
7
+ * - LINEAR-style microtask batching (transactions in same event loop share batchId)
8
+ * - Proper dependency injection (no singleton)
9
+ */
10
+ import { EventEmitter } from 'events';
11
+ import type { Database } from '../Database.js';
12
+ import { Model } from '../Model.js';
13
+ import type { MutationOptions } from '../interfaces/index.js';
14
+ export interface UserContext {
15
+ userId: string;
16
+ organizationId: string;
17
+ role?: string;
18
+ teamIds?: string[];
19
+ }
20
+ /** Wire-format mutation payload (post-projection). */
21
+ type MutationInput = Record<string, unknown>;
22
+ type TransactionWriteOptions = Pick<MutationOptions, 'readAt' | 'onStale'>;
23
+ export interface Transaction {
24
+ id: string;
25
+ type: 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
26
+ modelName: string;
27
+ modelId: string;
28
+ modelKey: string;
29
+ data?: MutationInput;
30
+ previousData?: MutationInput | null;
31
+ context: UserContext;
32
+ status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed' | 'rolled_back';
33
+ createdAt: number;
34
+ attempts: number;
35
+ priority: 'normal' | 'high';
36
+ priorityScore: number;
37
+ writeOptions?: TransactionWriteOptions;
38
+ batchId?: string;
39
+ /** LINEAR PATTERN: syncId threshold - transaction confirms when delta.id >= this value */
40
+ syncIdNeededForCompletion?: number;
41
+ /**
42
+ * Resolves when the server has confirmed this transaction (delta arrived
43
+ * or HTTP ack). Rejects with the originating error if the transaction is
44
+ * permanently rolled back. Name matches the queue's existing `'confirmed'`
45
+ * status vocabulary (`commits.create({wait:'confirmed'})`,
46
+ * `waitForConfirmation`) — gives call sites a single `await` point for
47
+ * "did my write land?", so failures surface at the source instead of
48
+ * leaking via silent pool rollback. The rejection error is the same
49
+ * `AbloError` recorded on the queue's `transaction:failed` event.
50
+ */
51
+ confirmation?: Promise<void>;
52
+ }
53
+ /**
54
+ * A raw multi-op commit transaction queued via `ablo.commits.create()`.
55
+ *
56
+ * Distinct from the per-model `Transaction` above: operations are
57
+ * pre-built by the caller and the envelope is atomic — no coalescing,
58
+ * no FK reordering, no optimistic local apply. The lane shares the
59
+ * same `mutationExecutor.commit()` underneath as the model-proxy
60
+ * batch path, so reconnect-retry behavior is identical.
61
+ */
62
+ interface CommitTransaction {
63
+ id: string;
64
+ kind: 'commit';
65
+ operations: Array<{
66
+ type: string;
67
+ model: string;
68
+ id: string;
69
+ input?: Record<string, unknown>;
70
+ transactionId?: string;
71
+ readAt?: number | null;
72
+ onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
73
+ }>;
74
+ causedByTaskId?: string | null;
75
+ status: 'pending' | 'executing' | 'completed' | 'failed';
76
+ createdAt: number;
77
+ attempts: number;
78
+ lastSyncId?: number;
79
+ error?: Error;
80
+ }
81
+ interface ConflictResolution {
82
+ strategy: 'last-write-wins' | 'merge' | 'reject' | 'custom';
83
+ resolver?: (local: MutationInput | undefined, remote: MutationInput) => MutationInput;
84
+ }
85
+ interface TransactionQueueConfig {
86
+ maxBatchSize: number;
87
+ batchDelay: number;
88
+ maxRetries: number;
89
+ conflictResolution: ConflictResolution;
90
+ enablePersistence: boolean;
91
+ enableOptimistic: boolean;
92
+ maxExecutingTransactions: number;
93
+ deltaConfirmationTimeout: number;
94
+ /**
95
+ * Exponential backoff for retryable server responses (HTTP 429/503).
96
+ * `baseMs` is the first retry delay; each subsequent attempt doubles
97
+ * up to `capMs`. Final delay = min(capMs, baseMs * 2^(attempt-1)) +
98
+ * up to 100ms of jitter. Defaults: 200ms / 1500ms.
99
+ */
100
+ retryBackoff: {
101
+ baseMs: number;
102
+ capMs: number;
103
+ };
104
+ /**
105
+ * Grace window in ms before in-flight commit-lane transactions are
106
+ * failed with `AbloConnectionError` after the WebSocket transitions
107
+ * to `'disconnected'`. Brief disconnects (deploy rotations, mobile
108
+ * jitter) are absorbed transparently; only persistent disconnects
109
+ * surface as failures. Aligned with the 30s convention from the
110
+ * WebSocket reconnection guidance (websocket.org). Set lower for
111
+ * human-interactive consumers (e.g. 10s for chat) or higher for
112
+ * batch workers (e.g. 60s for agent-worker).
113
+ *
114
+ * Without this deadline, `commits.create({wait:'confirmed'})` waits
115
+ * forever when the WS dies mid-flight — see the 2026-05-15 wedge.
116
+ */
117
+ commitOfflineGraceMs: number;
118
+ }
119
+ export declare class TransactionQueue extends EventEmitter {
120
+ private store;
121
+ private _mutationExecutor;
122
+ private get mutationExecutor();
123
+ private executionQueue;
124
+ private isProcessing;
125
+ private processTimer?;
126
+ private processScheduled;
127
+ private createdTransactions;
128
+ private commitScheduled;
129
+ private inFlightByModel;
130
+ private pendingMergeByModel;
131
+ private commitLane;
132
+ private commitStore;
133
+ private commitProcessing;
134
+ private computePriorityScore;
135
+ private ensureDerivedFields;
136
+ private mergeUpdateData;
137
+ private config;
138
+ private executingCount;
139
+ private optimisticUpdates;
140
+ private deltaConfirmationTimeouts;
141
+ private deltaConfirmationRetries;
142
+ private isConnectedFn;
143
+ private commitOfflineGraceTimer;
144
+ private lastSeenSyncId;
145
+ private static readonly DELTA_MAX_RETRIES;
146
+ private static readonly DELTA_INITIAL_TIMEOUT_MS;
147
+ private static readonly DELTA_MAX_TIMEOUT_MS;
148
+ private batchIndex;
149
+ /**
150
+ * Resolvers for per-transaction `confirmation` promises. Populated in
151
+ * `attachConfirmation` at staging time, consumed by the constructor-time
152
+ * listeners on `transaction:completed` / `transaction:failed`. Kept off
153
+ * the Transaction row so the store's iteration order stays plain-data
154
+ * and serialization-friendly.
155
+ */
156
+ private confirmationResolvers;
157
+ constructor(config?: Partial<TransactionQueueConfig>);
158
+ /**
159
+ * Look up the in-flight `confirmation` promise for a (model, id) pair.
160
+ * Returns the promise from the most-recent live transaction matching
161
+ * the given model+id, or `Promise.resolve()` if none is open (which
162
+ * means either "already confirmed" or "never staged" — both safe
163
+ * outcomes for the routing-helper grace-window use case).
164
+ *
165
+ * Looks across `pending`, `executing`, and `awaiting_delta` — these
166
+ * are the three non-terminal statuses where rollback is still
167
+ * possible. Skips `completed` (already settled) and `failed` /
168
+ * `rolled_back` (already rejected; the call site missed the
169
+ * `confirmation` window and should rely on `onMutationFailure` toast
170
+ * instead).
171
+ *
172
+ * Distinct from `tx.confirmation` on a known transaction — used by
173
+ * call sites that hold a Model reference (returned by
174
+ * `ablo.<model>.create()`) but never see the underlying transaction.
175
+ */
176
+ confirmationFor(modelName: string, modelId: string): Promise<void>;
177
+ /**
178
+ * Attach a hot `confirmation` promise to a freshly created transaction.
179
+ * Must be called BEFORE the transaction is staged so the call site can
180
+ * `await tx.confirmation` synchronously after the create/update/delete
181
+ * call returns. Idempotent: returns early if the tx already has one.
182
+ *
183
+ * The unhandled-rejection trap is mandatory — most call sites won't
184
+ * `await confirmation`, and Node/browser would otherwise crash on the
185
+ * rejection. Consumers who *do* want failure visibility just attach a
186
+ * `.then`/`.catch` and the trap becomes a no-op.
187
+ */
188
+ private attachConfirmation;
189
+ /**
190
+ * Set connection state checker - prevents rollbacks during disconnection.
191
+ * When disconnected, timeouts re-schedule instead of rolling back.
192
+ */
193
+ setConnectionChecker(fn: () => boolean): void;
194
+ /**
195
+ * Drive the offline-grace timer for in-flight commit-lane transactions.
196
+ *
197
+ * On `'disconnected'`: start a one-shot timer of
198
+ * `config.commitOfflineGraceMs`. If the timer fires (disconnect
199
+ * persisted past grace), iterate every commit-lane transaction with
200
+ * `status ∈ {'pending', 'executing'}` and emit
201
+ * `transaction:failed:${id}` with an `AbloConnectionError`. That
202
+ * lets `waitForCommitReceipt` reject in seconds instead of hanging
203
+ * forever — which is what wedged the 2026-05-15 subagent run.
204
+ *
205
+ * On `'connected'`: clear any pending grace timer. Brief blips are
206
+ * absorbed transparently; the existing reconnect-retry path in
207
+ * `processCommitLane` / `flushOfflineQueue` handles the resumption.
208
+ *
209
+ * Called from SyncClient's `setConnectionState` after the
210
+ * `'connection:disconnected'` / `'connection:established'` events.
211
+ */
212
+ setConnectionState(state: 'connected' | 'disconnected'): void;
213
+ private failInFlightCommitsOnOffline;
214
+ /**
215
+ * Bind the executor for this queue instance. Called by the owning Ablo
216
+ * right after `BaseSyncedStore` is constructed so the executor's
217
+ * `storeHolder.store` closure resolves to *this* Ablo's WS — not whichever
218
+ * Ablo most recently called `initSyncEngine()`.
219
+ */
220
+ setMutationExecutor(executor: import('../interfaces/index.js').MutationExecutor): void;
221
+ /**
222
+ * Stage a transaction for commit (Linear pattern)
223
+ * Transactions staged in the same event loop tick will be committed together
224
+ */
225
+ private stageTransaction;
226
+ /**
227
+ * Schedule commit of staged transactions via microtask
228
+ * This ensures all synchronous transaction creates are batched together
229
+ */
230
+ private scheduleCommit;
231
+ /**
232
+ * Commit all staged transactions to the execution queue (Linear pattern)
233
+ * All transactions get the same batchIndex for efficient batching
234
+ */
235
+ private commitCreatedTransactions;
236
+ flushOfflineQueue(): Promise<void>;
237
+ /**
238
+ * Create operation with optimistic update
239
+ */
240
+ create(model: Model, context: UserContext, writeOptions?: TransactionWriteOptions): Promise<Transaction>;
241
+ /**
242
+ * Update operation with conflict detection
243
+ * @param precomputedChanges - Optional pre-captured changes (avoids re-reading from model)
244
+ */
245
+ update(model: Model, context: UserContext, precomputedChanges?: Record<string, unknown>, writeOptions?: TransactionWriteOptions): Promise<Transaction>;
246
+ /**
247
+ * Delete operation with cascade handling
248
+ */
249
+ delete(model: Model, context: UserContext, writeOptions?: TransactionWriteOptions): Promise<Transaction>;
250
+ /**
251
+ * Upload attachment — delegates to attachment-uploader.ts
252
+ */
253
+ uploadAttachment(_file: File, options: {
254
+ id: string;
255
+ [key: string]: unknown;
256
+ }, _context: UserContext): Promise<{
257
+ url: string;
258
+ } | null>;
259
+ /**
260
+ * Batch upload attachments — delegates to MutationExecutor
261
+ */
262
+ batchUploadAttachments(_files: File[], items: Array<{
263
+ id: string;
264
+ [key: string]: unknown;
265
+ }>, _context: UserContext): Promise<Array<{
266
+ id: string;
267
+ url: string;
268
+ }>>;
269
+ /**
270
+ * Archive operation
271
+ */
272
+ archive(model: Model, context: UserContext, writeOptions?: TransactionWriteOptions): Promise<Transaction>;
273
+ /**
274
+ * Unarchive operation
275
+ */
276
+ unarchive(model: Model, context: UserContext): Promise<Transaction>;
277
+ /**
278
+ * Enqueue transaction for execution
279
+ */
280
+ private enqueue;
281
+ private scheduleProcessing;
282
+ /**
283
+ * Process batch of transactions using LINEAR-style unified batch execution.
284
+ *
285
+ * Key optimization: Instead of making separate calls per operation type/model,
286
+ * we collect ALL batchable operations and send them in a SINGLE commit call.
287
+ * The sync-server handles mixed types atomically inside one transaction.
288
+ *
289
+ * This reduces N round-trips to 1, dramatically improving batch latency.
290
+ */
291
+ private processBatch;
292
+ /**
293
+ * LINEAR PATTERN: Confirm all awaiting transactions when delta with syncId >= threshold arrives.
294
+ * This replaces clientMutationId echoing - transactions are confirmed by sync ID threshold.
295
+ * @param syncId - The sync ID of the received delta
296
+ */
297
+ onDeltaReceived(syncId: number): void;
298
+ private scheduleDeltaConfirmationTimeout;
299
+ private cancelDeltaConfirmationTimeout;
300
+ /**
301
+ * Wait for a transaction to be confirmed via delta echo (Linear pattern)
302
+ * Reuses existing timeout mechanism from scheduleDeltaConfirmationTimeout
303
+ */
304
+ waitForConfirmation(transactionId: string): Promise<void>;
305
+ hasClientMutationId(id: string): boolean;
306
+ /**
307
+ * Enqueue a raw multi-op atomic commit envelope (the `ablo.commits.create`
308
+ * path). Operations are pre-built by the caller; the queue's job is
309
+ * retry-on-reconnect + idempotent dedup, NOT optimistic apply or FK
310
+ * ordering. Same idempotency key (clientTxId) is dropped on the floor
311
+ * if already in flight — server-side `mutation_log` handles cross-session
312
+ * dedup; this guard handles same-session double-enqueue.
313
+ */
314
+ enqueueCommit(clientTxId: string, operations: CommitTransaction['operations'], options?: {
315
+ causedByTaskId?: string | null;
316
+ }): void;
317
+ /**
318
+ * Drain pending commit-lane envelopes serially. Transient failures
319
+ * (network, ws_not_ready) leave the head-of-queue tx in `pending` and
320
+ * break — reconnect handler re-kicks via `flushOfflineQueue`.
321
+ * Permanent failures emit `transaction:failed:<id>` and drop the tx.
322
+ */
323
+ private processCommitLane;
324
+ /**
325
+ * Promise-based confirmation for a commit-lane transaction. Resolves
326
+ * with the server-side `lastSyncId` once `mutation_result` lands;
327
+ * rejects on permanent failure. Backs the `wait: 'confirmed'` semantics
328
+ * of `ablo.commits.create()`.
329
+ */
330
+ waitForCommitReceipt(clientTxId: string): Promise<{
331
+ lastSyncId: number;
332
+ }>;
333
+ private isReorderPayload;
334
+ /**
335
+ * Determine if an error is transient (retryable) vs permanent (non-retryable).
336
+ *
337
+ * IMPORTANT: Uses a BLOCKLIST approach for safety - only retry on known transient errors.
338
+ * Any unknown error type defaults to permanent (don't retry) to prevent infinite loops.
339
+ *
340
+ * Transient errors (will retry):
341
+ * - Network failures, connection errors, timeouts
342
+ * - Server errors (5xx status codes)
343
+ * - Rate limiting (429)
344
+ *
345
+ * Permanent errors (won't retry - includes but not limited to):
346
+ * - Validation errors, constraint violations
347
+ * - Not found, unauthorized, forbidden
348
+ * - Any other business logic error from the server
349
+ */
350
+ private isPermanentError;
351
+ /**
352
+ * Handle transaction failure
353
+ */
354
+ private handleFailure;
355
+ /**
356
+ * Conflict resolution
357
+ */
358
+ handleConflict(transaction: Transaction, serverData: MutationInput): Promise<void>;
359
+ /**
360
+ * Optimistic updates
361
+ */
362
+ private applyOptimisticCreate;
363
+ private applyOptimisticUpdate;
364
+ private applyOptimisticDelete;
365
+ private rollbackOptimistic;
366
+ /**
367
+ * Execute individual transaction via the unified commit path
368
+ */
369
+ private executeTransaction;
370
+ /**
371
+ * Persistence
372
+ */
373
+ loadPersistedTransactions(database: Database): Promise<void>;
374
+ private deserializeTransaction;
375
+ /**
376
+ * Cancel transactions for a specific model
377
+ */
378
+ cancelTransactionsForModel(modelId: string, transactionType?: string): Transaction[];
379
+ /**
380
+ * LINEAR PATTERN: Cancel transactions for child entities by foreign key
381
+ *
382
+ * Used by SyncedStore for cascade cancellation when a parent is deleted.
383
+ * This keeps FK relationship knowledge in ModelRegistry/SyncedStore,
384
+ * while TransactionQueue just handles the cancellation mechanics.
385
+ *
386
+ * @param childModelName - The child model type (e.g., 'SlideLayer')
387
+ * @param foreignKey - The FK property name (e.g., 'slideId')
388
+ * @param parentId - The deleted parent's ID
389
+ * @returns Number of transactions cancelled
390
+ */
391
+ cancelTransactionsByForeignKey(childModelName: string, foreignKey: string, parentId: string): number;
392
+ /**
393
+ * Get count of outstanding transactions
394
+ */
395
+ getOutstandingTransactionCount(): number;
396
+ /**
397
+ * Utilities
398
+ */
399
+ private generateId;
400
+ private mergeData;
401
+ private extractCreateData;
402
+ private mapChangesToInput;
403
+ private extractUpdateData;
404
+ private buildUpdateInput;
405
+ private extractPreviousData;
406
+ /**
407
+ * Public API
408
+ */
409
+ getStats(): {
410
+ pending: number;
411
+ executing: number;
412
+ completed: number;
413
+ failed: number;
414
+ optimistic: number;
415
+ totalTransactions: number;
416
+ batchIndex: number;
417
+ config: {
418
+ maxBatchSize: number;
419
+ batchDelay: number;
420
+ maxRetries: number;
421
+ conflictResolution: ConflictResolution;
422
+ enablePersistence: boolean;
423
+ enableOptimistic: boolean;
424
+ maxExecutingTransactions: number;
425
+ deltaConfirmationTimeout: number;
426
+ /**
427
+ * Exponential backoff for retryable server responses (HTTP 429/503).
428
+ * `baseMs` is the first retry delay; each subsequent attempt doubles
429
+ * up to `capMs`. Final delay = min(capMs, baseMs * 2^(attempt-1)) +
430
+ * up to 100ms of jitter. Defaults: 200ms / 1500ms.
431
+ */
432
+ retryBackoff: {
433
+ baseMs: number;
434
+ capMs: number;
435
+ };
436
+ /**
437
+ * Grace window in ms before in-flight commit-lane transactions are
438
+ * failed with `AbloConnectionError` after the WebSocket transitions
439
+ * to `'disconnected'`. Brief disconnects (deploy rotations, mobile
440
+ * jitter) are absorbed transparently; only persistent disconnects
441
+ * surface as failures. Aligned with the 30s convention from the
442
+ * WebSocket reconnection guidance (websocket.org). Set lower for
443
+ * human-interactive consumers (e.g. 10s for chat) or higher for
444
+ * batch workers (e.g. 60s for agent-worker).
445
+ *
446
+ * Without this deadline, `commits.create({wait:'confirmed'})` waits
447
+ * forever when the WS dies mid-flight — see the 2026-05-15 wedge.
448
+ */
449
+ commitOfflineGraceMs: number;
450
+ };
451
+ };
452
+ /**
453
+ * Get detailed debug info for the sync debug page
454
+ * Exposes internal state that helps diagnose delta confirmation issues
455
+ */
456
+ getDebugInfo(): {
457
+ lastSeenSyncId: number;
458
+ awaitingDeltaCount: number;
459
+ awaitingDeltaTransactions: {
460
+ id: string;
461
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
462
+ modelName: string;
463
+ modelId: string;
464
+ syncIdNeeded: number | undefined;
465
+ createdAt: number;
466
+ age: number;
467
+ }[];
468
+ pendingTransactions: {
469
+ id: string;
470
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
471
+ modelName: string;
472
+ modelId: string;
473
+ }[];
474
+ executingTransactions: {
475
+ id: string;
476
+ type: "update" | "create" | "delete" | "archive" | "unarchive";
477
+ modelName: string;
478
+ modelId: string;
479
+ }[];
480
+ };
481
+ /**
482
+ * Set configuration
483
+ */
484
+ setConfig(config: Partial<TransactionQueueConfig>): void;
485
+ /**
486
+ * Handle incoming sync delta - simplified for permanent IDs
487
+ */
488
+ handleSyncDelta(delta: {
489
+ id: string;
490
+ modelName: string;
491
+ action: string;
492
+ data: any;
493
+ }): boolean;
494
+ /**
495
+ * Cleanup and dispose resources
496
+ */
497
+ dispose(): void;
498
+ }
499
+ export {};