@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,481 @@
1
+ /**
2
+ * Linear Sync Engine - Sync Action Store
3
+ *
4
+ * Stores and manages sync actions received from the server.
5
+ * Critical for delta sync and maintaining sync state consistency.
6
+ */
7
+ /**
8
+ * SyncActionStore - Manages sync actions (deltas)
9
+ *
10
+ * Features:
11
+ * - Stores sync actions by ID for replay
12
+ * - Tracks applied vs pending actions
13
+ * - Enables rewind/replay for conflict resolution
14
+ * - Maintains sync watermark
15
+ */
16
+ export class SyncActionStore {
17
+ db;
18
+ storeName = 'sync_action_table';
19
+ lastAppliedSyncId = 0;
20
+ pendingActions = new Map();
21
+ constructor(db) {
22
+ this.db = db;
23
+ }
24
+ /**
25
+ * Initialize store (create if needed)
26
+ */
27
+ async initialize() {
28
+ // Store is created during database migration
29
+ // Load last applied sync ID from metadata
30
+ const metadata = await this.getMetadata();
31
+ if (metadata) {
32
+ this.lastAppliedSyncId = metadata.lastSyncId || 0;
33
+ }
34
+ }
35
+ /**
36
+ * Store a sync action
37
+ */
38
+ async storeSyncAction(action) {
39
+ return new Promise((resolve, reject) => {
40
+ const tx = this.db.transaction([this.storeName], 'readwrite');
41
+ const store = tx.objectStore(this.storeName);
42
+ const request = store.put({
43
+ ...action,
44
+ storedAt: Date.now(),
45
+ applied: false,
46
+ });
47
+ tx.oncomplete = () => {
48
+ // Add to pending if not yet applied
49
+ if (action.id > this.lastAppliedSyncId) {
50
+ this.pendingActions.set(action.id, action);
51
+ }
52
+ resolve();
53
+ };
54
+ tx.onerror = () => reject(tx.error);
55
+ request.onerror = () => reject(request.error);
56
+ });
57
+ }
58
+ /**
59
+ * Store multiple sync actions
60
+ */
61
+ async storeSyncActions(actions) {
62
+ if (actions.length === 0)
63
+ return;
64
+ return new Promise((resolve, reject) => {
65
+ const tx = this.db.transaction([this.storeName], 'readwrite');
66
+ const store = tx.objectStore(this.storeName);
67
+ let completed = 0;
68
+ const total = actions.length;
69
+ for (const action of actions) {
70
+ const request = store.put({
71
+ ...action,
72
+ storedAt: Date.now(),
73
+ applied: false,
74
+ });
75
+ request.onsuccess = () => {
76
+ completed++;
77
+ if (completed === total) {
78
+ // Add to pending
79
+ for (const action of actions) {
80
+ if (action.id > this.lastAppliedSyncId) {
81
+ this.pendingActions.set(action.id, action);
82
+ }
83
+ }
84
+ }
85
+ };
86
+ request.onerror = () => reject(request.error);
87
+ }
88
+ tx.oncomplete = () => resolve();
89
+ tx.onerror = () => reject(tx.error);
90
+ });
91
+ }
92
+ /**
93
+ * Get sync action by ID
94
+ */
95
+ async getSyncAction(id) {
96
+ return new Promise((resolve, reject) => {
97
+ const tx = this.db.transaction([this.storeName], 'readonly');
98
+ const store = tx.objectStore(this.storeName);
99
+ const request = store.get(id);
100
+ request.onsuccess = () => {
101
+ const data = request.result;
102
+ if (!data) {
103
+ resolve(undefined);
104
+ return;
105
+ }
106
+ const { storedAt, applied, ...action } = data;
107
+ resolve(action);
108
+ };
109
+ request.onerror = () => reject(request.error);
110
+ });
111
+ }
112
+ /**
113
+ * Get sync actions in range
114
+ */
115
+ async getSyncActionsInRange(startId, endId) {
116
+ return new Promise((resolve, reject) => {
117
+ const tx = this.db.transaction([this.storeName], 'readonly');
118
+ const store = tx.objectStore(this.storeName);
119
+ const index = store.index('syncId');
120
+ const range = IDBKeyRange.bound(startId, endId);
121
+ const request = index.getAll(range);
122
+ request.onsuccess = () => {
123
+ const allData = request.result;
124
+ const actions = allData.map(({ storedAt, applied, ...action }) => action);
125
+ resolve(actions);
126
+ };
127
+ request.onerror = () => reject(request.error);
128
+ });
129
+ }
130
+ /**
131
+ * Get pending sync actions (not yet applied)
132
+ */
133
+ async getPendingSyncActions() {
134
+ if (this.pendingActions.size === 0) {
135
+ return [];
136
+ }
137
+ // Return sorted by sync ID
138
+ const sorted = Array.from(this.pendingActions.values()).sort((a, b) => a.id - b.id);
139
+ return sorted;
140
+ }
141
+ /**
142
+ * Mark sync action as applied
143
+ */
144
+ async markAsApplied(syncId) {
145
+ return new Promise(async (resolve, reject) => {
146
+ const tx = this.db.transaction([this.storeName], 'readwrite');
147
+ const store = tx.objectStore(this.storeName);
148
+ const getRequest = store.get(syncId);
149
+ getRequest.onsuccess = () => {
150
+ const existing = getRequest.result;
151
+ if (existing) {
152
+ existing.applied = true;
153
+ existing.appliedAt = Date.now();
154
+ const putRequest = store.put(existing);
155
+ putRequest.onerror = () => reject(putRequest.error);
156
+ }
157
+ };
158
+ getRequest.onerror = () => reject(getRequest.error);
159
+ tx.oncomplete = async () => {
160
+ // Update tracking
161
+ if (syncId > this.lastAppliedSyncId) {
162
+ this.lastAppliedSyncId = syncId;
163
+ }
164
+ // Remove from pending
165
+ this.pendingActions.delete(syncId);
166
+ // Update metadata
167
+ try {
168
+ await this.updateLastSyncId(syncId);
169
+ resolve();
170
+ }
171
+ catch (error) {
172
+ reject(error);
173
+ }
174
+ };
175
+ tx.onerror = () => reject(tx.error);
176
+ });
177
+ }
178
+ /**
179
+ * Mark multiple actions as applied
180
+ */
181
+ async markManyAsApplied(syncIds) {
182
+ if (syncIds.length === 0)
183
+ return;
184
+ return new Promise(async (resolve, reject) => {
185
+ const tx = this.db.transaction([this.storeName], 'readwrite');
186
+ const store = tx.objectStore(this.storeName);
187
+ let processed = 0;
188
+ const total = syncIds.length;
189
+ for (const syncId of syncIds) {
190
+ const getRequest = store.get(syncId);
191
+ getRequest.onsuccess = () => {
192
+ const existing = getRequest.result;
193
+ if (existing) {
194
+ existing.applied = true;
195
+ existing.appliedAt = Date.now();
196
+ const putRequest = store.put(existing);
197
+ putRequest.onsuccess = () => {
198
+ processed++;
199
+ // Remove from pending
200
+ this.pendingActions.delete(syncId);
201
+ if (processed === total) {
202
+ // All processed, update last applied ID
203
+ const maxId = Math.max(...syncIds);
204
+ if (maxId > this.lastAppliedSyncId) {
205
+ this.lastAppliedSyncId = maxId;
206
+ }
207
+ }
208
+ };
209
+ putRequest.onerror = () => reject(putRequest.error);
210
+ }
211
+ else {
212
+ processed++;
213
+ this.pendingActions.delete(syncId);
214
+ }
215
+ };
216
+ getRequest.onerror = () => reject(getRequest.error);
217
+ }
218
+ tx.oncomplete = async () => {
219
+ // Update metadata with the highest sync ID
220
+ const maxId = Math.max(...syncIds);
221
+ if (maxId > this.lastAppliedSyncId) {
222
+ try {
223
+ await this.updateLastSyncId(maxId);
224
+ resolve();
225
+ }
226
+ catch (error) {
227
+ reject(error);
228
+ }
229
+ }
230
+ else {
231
+ resolve();
232
+ }
233
+ };
234
+ tx.onerror = () => reject(tx.error);
235
+ });
236
+ }
237
+ /**
238
+ * Get last applied sync ID
239
+ */
240
+ getLastAppliedSyncId() {
241
+ return this.lastAppliedSyncId;
242
+ }
243
+ /**
244
+ * Check if we have a gap in sync IDs
245
+ */
246
+ async hasGap(fromId, toId) {
247
+ return new Promise((resolve, reject) => {
248
+ const tx = this.db.transaction([this.storeName], 'readonly');
249
+ const store = tx.objectStore(this.storeName);
250
+ const index = store.index('syncId');
251
+ let currentId = fromId;
252
+ let hasGap = false;
253
+ const checkNext = () => {
254
+ if (currentId > toId) {
255
+ resolve(hasGap);
256
+ return;
257
+ }
258
+ const request = index.get(currentId);
259
+ request.onsuccess = () => {
260
+ if (!request.result) {
261
+ hasGap = true;
262
+ resolve(true);
263
+ return;
264
+ }
265
+ currentId++;
266
+ checkNext();
267
+ };
268
+ request.onerror = () => reject(request.error);
269
+ };
270
+ checkNext();
271
+ });
272
+ }
273
+ /**
274
+ * Get missing sync IDs in range
275
+ */
276
+ async getMissingSyncIds(fromId, toId) {
277
+ return new Promise((resolve, reject) => {
278
+ const missing = [];
279
+ const tx = this.db.transaction([this.storeName], 'readonly');
280
+ const store = tx.objectStore(this.storeName);
281
+ const index = store.index('syncId');
282
+ let currentId = fromId;
283
+ const checkNext = () => {
284
+ if (currentId > toId) {
285
+ resolve(missing);
286
+ return;
287
+ }
288
+ const request = index.get(currentId);
289
+ request.onsuccess = () => {
290
+ if (!request.result) {
291
+ missing.push(currentId);
292
+ }
293
+ currentId++;
294
+ checkNext();
295
+ };
296
+ request.onerror = () => reject(request.error);
297
+ };
298
+ checkNext();
299
+ });
300
+ }
301
+ /**
302
+ * Clean up old sync actions
303
+ */
304
+ async cleanup(keepDays = 7) {
305
+ return new Promise((resolve, reject) => {
306
+ const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1000;
307
+ const tx = this.db.transaction([this.storeName], 'readwrite');
308
+ const store = tx.objectStore(this.storeName);
309
+ const request = store.openCursor();
310
+ let cleaned = 0;
311
+ request.onsuccess = (event) => {
312
+ const cursor = event.target.result;
313
+ if (cursor) {
314
+ if (cursor.value.applied && cursor.value.appliedAt < cutoff) {
315
+ const deleteRequest = cursor.delete();
316
+ deleteRequest.onsuccess = () => {
317
+ cleaned++;
318
+ cursor.continue();
319
+ };
320
+ deleteRequest.onerror = () => reject(deleteRequest.error);
321
+ }
322
+ else {
323
+ cursor.continue();
324
+ }
325
+ }
326
+ else {
327
+ // Cursor finished
328
+ resolve(cleaned);
329
+ }
330
+ };
331
+ request.onerror = () => reject(request.error);
332
+ });
333
+ }
334
+ /**
335
+ * Clear all sync actions
336
+ */
337
+ async clear() {
338
+ return new Promise((resolve, reject) => {
339
+ const tx = this.db.transaction([this.storeName], 'readwrite');
340
+ const store = tx.objectStore(this.storeName);
341
+ const request = store.clear();
342
+ tx.oncomplete = () => {
343
+ this.pendingActions.clear();
344
+ this.lastAppliedSyncId = 0;
345
+ resolve();
346
+ };
347
+ tx.onerror = () => reject(tx.error);
348
+ request.onerror = () => reject(request.error);
349
+ });
350
+ }
351
+ /**
352
+ * Get statistics
353
+ */
354
+ async getStats() {
355
+ return new Promise((resolve, reject) => {
356
+ const tx = this.db.transaction([this.storeName], 'readonly');
357
+ const store = tx.objectStore(this.storeName);
358
+ const request = store.getAll();
359
+ request.onsuccess = () => {
360
+ const allActions = request.result;
361
+ const applied = allActions.filter((a) => a.applied).length;
362
+ const pending = allActions.filter((a) => !a.applied).length;
363
+ const timestamps = allActions
364
+ .map((a) => a.storedAt)
365
+ .filter(Boolean)
366
+ .sort();
367
+ resolve({
368
+ total: allActions.length,
369
+ applied,
370
+ pending,
371
+ lastAppliedId: this.lastAppliedSyncId,
372
+ oldestAction: timestamps.length > 0 ? new Date(timestamps[0]) : null,
373
+ newestAction: timestamps.length > 0 ? new Date(timestamps[timestamps.length - 1]) : null,
374
+ });
375
+ };
376
+ request.onerror = () => reject(request.error);
377
+ });
378
+ }
379
+ /**
380
+ * Update last sync ID in metadata
381
+ */
382
+ async updateLastSyncId(syncId) {
383
+ return new Promise((resolve, reject) => {
384
+ const tx = this.db.transaction(['__meta'], 'readwrite');
385
+ const store = tx.objectStore('__meta');
386
+ const getRequest = store.get('metadata');
387
+ getRequest.onsuccess = () => {
388
+ const metadata = getRequest.result || {};
389
+ metadata.lastSyncId = syncId;
390
+ metadata.updatedAt = new Date();
391
+ const putRequest = store.put(metadata, 'metadata');
392
+ putRequest.onerror = () => reject(putRequest.error);
393
+ };
394
+ getRequest.onerror = () => reject(getRequest.error);
395
+ tx.oncomplete = () => resolve();
396
+ tx.onerror = () => reject(tx.error);
397
+ });
398
+ }
399
+ /**
400
+ * Get metadata. Shape mirrors what's written by the database manager
401
+ * — currently only `lastSyncId` is consumed here (in `initialize`),
402
+ * so the return type is narrowed to that read surface. Returns
403
+ * `undefined` when the row hasn't been written yet (fresh DB).
404
+ */
405
+ async getMetadata() {
406
+ return new Promise((resolve, reject) => {
407
+ const tx = this.db.transaction(['__meta'], 'readonly');
408
+ const store = tx.objectStore('__meta');
409
+ const request = store.get('metadata');
410
+ request.onsuccess = () => {
411
+ const raw = request.result;
412
+ if (raw && typeof raw === 'object' && 'lastSyncId' in raw) {
413
+ resolve(raw);
414
+ }
415
+ else {
416
+ resolve(undefined);
417
+ }
418
+ };
419
+ request.onerror = () => reject(request.error);
420
+ });
421
+ }
422
+ /**
423
+ * Rewind to a specific sync ID (for conflict resolution)
424
+ */
425
+ async rewindTo(syncId) {
426
+ return new Promise(async (resolve, reject) => {
427
+ // Get all actions after this sync ID
428
+ const tx = this.db.transaction([this.storeName], 'readonly');
429
+ const store = tx.objectStore(this.storeName);
430
+ const index = store.index('syncId');
431
+ const range = IDBKeyRange.lowerBound(syncId, false);
432
+ const request = index.getAll(range);
433
+ request.onsuccess = async () => {
434
+ const actionsToRewind = request.result;
435
+ // Mark them as not applied
436
+ const writeTx = this.db.transaction([this.storeName], 'readwrite');
437
+ const writeStore = writeTx.objectStore(this.storeName);
438
+ let processed = 0;
439
+ const total = actionsToRewind.length;
440
+ if (total === 0) {
441
+ this.lastAppliedSyncId = syncId - 1;
442
+ try {
443
+ await this.updateLastSyncId(this.lastAppliedSyncId);
444
+ resolve([]);
445
+ }
446
+ catch (error) {
447
+ reject(error);
448
+ }
449
+ return;
450
+ }
451
+ for (const action of actionsToRewind) {
452
+ action.applied = false;
453
+ delete action.appliedAt;
454
+ const putRequest = writeStore.put(action);
455
+ putRequest.onsuccess = () => {
456
+ processed++;
457
+ // Add back to pending
458
+ this.pendingActions.set(action.id, action);
459
+ if (processed === total) {
460
+ // All processed
461
+ this.lastAppliedSyncId = syncId - 1;
462
+ }
463
+ };
464
+ putRequest.onerror = () => reject(putRequest.error);
465
+ }
466
+ writeTx.oncomplete = async () => {
467
+ try {
468
+ await this.updateLastSyncId(this.lastAppliedSyncId);
469
+ const result = actionsToRewind.map(({ storedAt, applied, ...action }) => action);
470
+ resolve(result);
471
+ }
472
+ catch (error) {
473
+ reject(error);
474
+ }
475
+ };
476
+ writeTx.onerror = () => reject(writeTx.error);
477
+ };
478
+ request.onerror = () => reject(request.error);
479
+ });
480
+ }
481
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * BootstrapHelper - Fixed to always fetch fresh data
3
+ * Removed problematic caching that was serving stale data
4
+ */
5
+ export interface BootstrapData {
6
+ type: 'full' | 'partial';
7
+ lastSyncId: number;
8
+ /**
9
+ * Model rows keyed by typename. Each row is opaque to the SDK at this
10
+ * boundary — the per-model shape is asserted by the consumer (sync
11
+ * engine reduce + IDB write) using the registered schema.
12
+ */
13
+ models?: {
14
+ [typename: string]: unknown[];
15
+ };
16
+ deltas?: ValidatedServerDelta[];
17
+ deltaCount?: number;
18
+ /** Model types whose server-side query failed (timeout, RLS error, etc.) */
19
+ failedModels?: string[];
20
+ timestamp: number;
21
+ }
22
+ export interface BootstrapFetchResult {
23
+ notModified: boolean;
24
+ data?: BootstrapData;
25
+ etag?: string | null;
26
+ }
27
+ export interface BootstrapOptions {
28
+ /**
29
+ * Full base URL of the sync server's HTTP API, **including the `/api`
30
+ * prefix**. The bootstrap endpoint is appended as `/sync/bootstrap`, so
31
+ * the final request hits `${baseUrl}/sync/bootstrap`.
32
+ *
33
+ * Example: `'http://localhost:8080/api'` → `http://localhost:8080/api/sync/bootstrap`
34
+ *
35
+ * Default: `'http://localhost:8080/api'`.
36
+ */
37
+ baseUrl?: string;
38
+ /**
39
+ * Private cache namespace for offline bootstrap fallback. Hosted SDK
40
+ * callers do not pass this; Ablo sets it after auth resolves the
41
+ * account scope.
42
+ */
43
+ cacheScope?: string | null;
44
+ /**
45
+ * @deprecated Use `cacheScope`. Kept so older self-hosted code that
46
+ * still constructs BootstrapHelper directly keeps its cache namespace.
47
+ */
48
+ organizationId?: string;
49
+ syncGroups?: string[];
50
+ maxRetries?: number;
51
+ retryDelay?: number;
52
+ /** Timeout for individual fetch requests in ms (default: 30000) */
53
+ fetchTimeout?: number;
54
+ /**
55
+ * Model names to request in bootstrap. When set, the server only returns
56
+ * these models — everything else is skipped. Derived from the schema's
57
+ * `load` strategy: only models with `load: 'instant'` (or unset, which
58
+ * defaults to instant) are included.
59
+ *
60
+ * When absent, the server returns all models (backward compatible with
61
+ * old clients that don't send a models param).
62
+ */
63
+ instantModels?: string[];
64
+ }
65
+ import { type ValidatedServerDelta } from './schemas.js';
66
+ export declare class BootstrapHelper {
67
+ private options;
68
+ private abortController;
69
+ get baseUrl(): string;
70
+ constructor(options: BootstrapOptions);
71
+ /**
72
+ * Update the offline-cache namespace once auth has resolved the server-side
73
+ * account scope. This is intentionally not a public organizationId input.
74
+ */
75
+ setCacheScope(cacheScope: string): void;
76
+ setSyncGroups(syncGroups: readonly string[] | undefined): void;
77
+ setAuthToken(authToken: string | undefined): void;
78
+ /**
79
+ * Create a promise that rejects after a timeout
80
+ * Used to race against fetch requests that may hang indefinitely
81
+ */
82
+ private createTimeoutPromise;
83
+ /**
84
+ * Wrap a promise with a timeout - if the promise doesn't resolve within
85
+ * the timeout period, the AbortController is triggered and an error is thrown
86
+ */
87
+ private withTimeout;
88
+ /**
89
+ * Fetch bootstrap data from sync engine with partial bootstrap support
90
+ * @param lastSyncId - Optional: client's current lastSyncId for partial bootstrap
91
+ * @returns Bootstrap data (either full snapshot or delta batch)
92
+ */
93
+ fetchBootstrap(lastSyncId?: number): Promise<BootstrapData>;
94
+ /**
95
+ * Fetch bootstrap with ETag, returning 304 hints
96
+ */
97
+ fetchBootstrapWithETag(): Promise<BootstrapFetchResult>;
98
+ /**
99
+ * Perform the actual fetch request with timeout protection
100
+ */
101
+ private performFetch;
102
+ /**
103
+ * Fetch a single entity by ID (on-demand self-healing).
104
+ * Returns `null` for 404 (entity deleted) — this is an expected state, not an error.
105
+ * Throws for unexpected HTTP errors (5xx, network failures).
106
+ */
107
+ fetchEntity(modelName: string, id: string): Promise<Record<string, unknown> | null>;
108
+ /**
109
+ * Clear all cached bootstrap data
110
+ */
111
+ clearCache(): void;
112
+ private getBootstrapCacheKey;
113
+ private saveCachedBootstrap;
114
+ private loadCachedBootstrap;
115
+ /**
116
+ * Abort ongoing fetch request
117
+ */
118
+ abort(): void;
119
+ /**
120
+ * Helper to delay execution
121
+ */
122
+ private delay;
123
+ /**
124
+ * Get health status of sync engine
125
+ */
126
+ checkHealth(): Promise<boolean>;
127
+ }