@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,334 @@
1
+ /**
2
+ * Linear Sync Engine - Store Manager
3
+ *
4
+ * Manages all ObjectStore instances for registered models.
5
+ * Creates appropriate store types based on model load strategies.
6
+ * Follows Linear's architecture with 80+ ObjectStore instances.
7
+ */
8
+ import { ObjectStore } from '../stores/ObjectStore.js';
9
+ import { SyncActionStore } from '../stores/SyncActionStore.js';
10
+ import { LoadStrategy } from '../types/index.js';
11
+ import { AbloValidationError } from '../errors.js';
12
+ /**
13
+ * StoreManager - Central manager for all ObjectStore instances
14
+ *
15
+ * Key responsibilities:
16
+ * - Creates ObjectStore instances for each registered model
17
+ * - Manages store lifecycle and readiness
18
+ * - Provides unified interface for database operations
19
+ * - Handles store-specific optimizations based on load strategies
20
+ */
21
+ export class StoreManager {
22
+ stores = new Map();
23
+ syncactionStore = null;
24
+ db = null;
25
+ isInitialized = false;
26
+ modelRegistry;
27
+ constructor(modelRegistry) {
28
+ this.modelRegistry = modelRegistry;
29
+ }
30
+ /**
31
+ * Initialize all stores for registered models
32
+ */
33
+ async initializeStores(db) {
34
+ this.db = db;
35
+ if (this.isInitialized) {
36
+ getContext().logger.warn('StoreManager already initialized');
37
+ return;
38
+ }
39
+ getContext().logger.info('Initializing ObjectStore instances for all models');
40
+ const startTime = performance.now();
41
+ // Get all registered models
42
+ const allModels = this.modelRegistry.getRegisteredModelNames();
43
+ for (const modelName of allModels) {
44
+ await this.createStoreForModel(modelName);
45
+ }
46
+ // Initialize SyncactionStore
47
+ this.syncactionStore = new SyncActionStore(this.db);
48
+ await this.syncactionStore.initialize();
49
+ this.isInitialized = true;
50
+ const duration = performance.now() - startTime;
51
+ getContext().logger.info('Initialized ObjectStores and SyncactionStore', {
52
+ count: this.stores.size,
53
+ ms: duration.toFixed(2),
54
+ });
55
+ // Log store distribution
56
+ const storeTypes = this.getStoreTypeDistribution();
57
+ getContext().logger.debug('Store distribution', storeTypes);
58
+ }
59
+ /**
60
+ * Create ObjectStore for a specific model
61
+ */
62
+ async createStoreForModel(modelName) {
63
+ const metadata = this.modelRegistry.getMetadata(modelName);
64
+ if (!metadata) {
65
+ throw new AbloValidationError(`No metadata found for model: ${modelName}`, {
66
+ code: 'store_manager_unknown_model',
67
+ });
68
+ }
69
+ // Use model name directly as store name
70
+ const storeName = modelName;
71
+ // Create ObjectStore (MVP: simplified - use single store type for all strategies)
72
+ const store = new ObjectStore(this.db, modelName, storeName, metadata);
73
+ this.stores.set(modelName, store);
74
+ }
75
+ /**
76
+ * Create stores (tables) in IndexedDB
77
+ */
78
+ async createStores(db, transaction) {
79
+ getContext().logger.info('Creating tables for all registered models');
80
+ for (const modelName of this.modelRegistry.getRegisteredModelNames()) {
81
+ const storeName = modelName;
82
+ const metadata = this.modelRegistry.getMetadata(modelName);
83
+ // Skip if store already exists
84
+ if (db.objectStoreNames.contains(storeName)) {
85
+ continue;
86
+ }
87
+ getContext().logger.debug('Creating table', { storeName, modelName });
88
+ // Create object store with id as keyPath
89
+ const store = db.createObjectStore(storeName, { keyPath: 'id' });
90
+ // Create indexes for indexed properties
91
+ const indexedProperties = this.modelRegistry.getIndexedProperties(modelName);
92
+ for (const propName of indexedProperties) {
93
+ try {
94
+ store.createIndex(propName, propName, { unique: false });
95
+ getContext().logger.debug('Created index', { store: storeName, prop: propName });
96
+ }
97
+ catch (error) {
98
+ getContext().logger.warn('Failed to create index', { store: storeName, prop: propName, error });
99
+ }
100
+ }
101
+ // For partial load strategy models, we'll create additional partial index database later
102
+ if (metadata?.loadStrategy === LoadStrategy.partial) {
103
+ getContext().logger.debug('Model will have additional partial index database', { modelName });
104
+ }
105
+ }
106
+ // Create special tables
107
+ this.createSpecialTables(db);
108
+ }
109
+ /**
110
+ * Create special tables (sync_action_table, model_table, model_table_partial, __meta, __transactions)
111
+ */
112
+ createSpecialTables(db) {
113
+ // Create sync_action_table for sync actions (delta packets)
114
+ if (!db.objectStoreNames.contains('sync_action_table')) {
115
+ const syncActionStore = db.createObjectStore('sync_action_table', { keyPath: 'id' });
116
+ syncActionStore.createIndex('syncId', 'id');
117
+ getContext().logger.debug('Created sync_action_table');
118
+ }
119
+ // Create __meta table for model persistence state and database metadata
120
+ if (!db.objectStoreNames.contains('__meta')) {
121
+ const metaStore = db.createObjectStore('__meta');
122
+ getContext().logger.debug('Created __meta table');
123
+ }
124
+ // Create __transactions table for unsent transactions
125
+ if (!db.objectStoreNames.contains('__transactions')) {
126
+ const transactionStore = db.createObjectStore('__transactions', {
127
+ keyPath: 'id',
128
+ autoIncrement: false,
129
+ });
130
+ // Create indexes for transaction queries
131
+ transactionStore.createIndex('timestamp', 'timestamp');
132
+ transactionStore.createIndex('status', 'status');
133
+ getContext().logger.debug('Created __transactions table');
134
+ }
135
+ }
136
+ /**
137
+ * Get ObjectStore for a model
138
+ */
139
+ getStore(modelName) {
140
+ return this.stores.get(modelName);
141
+ }
142
+ /**
143
+ * Get SyncactionStore instance
144
+ */
145
+ getSyncactionStore() {
146
+ return this.syncactionStore;
147
+ }
148
+ /**
149
+ * Get all stores
150
+ */
151
+ getAllStores() {
152
+ return new Map(this.stores);
153
+ }
154
+ /**
155
+ * Check readiness of all stores
156
+ */
157
+ async checkReadinessOfStores() {
158
+ const readyStores = [];
159
+ const notReadyStores = [];
160
+ for (const [modelName, store] of Array.from(this.stores)) {
161
+ const isReady = await store.checkIsReady();
162
+ if (isReady) {
163
+ readyStores.push(modelName);
164
+ }
165
+ else {
166
+ notReadyStores.push(modelName);
167
+ }
168
+ }
169
+ const allReady = notReadyStores.length === 0;
170
+ getContext().logger.debug('Store readiness', {
171
+ ready: readyStores.length,
172
+ total: this.stores.size,
173
+ notReady: notReadyStores,
174
+ });
175
+ return {
176
+ ready: allReady,
177
+ readyStores,
178
+ notReadyStores,
179
+ totalStores: this.stores.size,
180
+ };
181
+ }
182
+ /**
183
+ * Check if ANY data store has at least one record.
184
+ *
185
+ * This is the Zero-style cache-validity check: if the stores are empty,
186
+ * the sync cursor (lastSyncId) is invalid regardless of what the metadata
187
+ * says. The cursor and the data must be co-located — no data means no
188
+ * cursor, which means full bootstrap.
189
+ *
190
+ * Samples up to 3 stores to avoid a full scan. If any store has records,
191
+ * returns true (we have cached data worth preserving).
192
+ */
193
+ async hasAnyData() {
194
+ const storeEntries = Array.from(this.stores);
195
+ // Sample a few stores — don't check all 30+ if the first one has data
196
+ const samplesToCheck = Math.min(storeEntries.length, 3);
197
+ for (let i = 0; i < samplesToCheck; i++) {
198
+ const [, store] = storeEntries[i];
199
+ try {
200
+ const count = await store.count();
201
+ if (count > 0)
202
+ return true;
203
+ }
204
+ catch {
205
+ // Store not accessible — treat as empty
206
+ }
207
+ }
208
+ return false;
209
+ }
210
+ /**
211
+ * Get store type distribution for debugging
212
+ */
213
+ getStoreTypeDistribution() {
214
+ let full = 0;
215
+ let partial = 0;
216
+ for (const [modelName] of Array.from(this.stores)) {
217
+ const metadata = this.modelRegistry.getMetadata(modelName);
218
+ if (metadata?.loadStrategy === LoadStrategy.partial) {
219
+ partial++;
220
+ }
221
+ else {
222
+ full++;
223
+ }
224
+ }
225
+ return { full, partial };
226
+ }
227
+ /**
228
+ * Get stores by load strategy
229
+ */
230
+ getStoresByStrategy(strategy) {
231
+ const stores = [];
232
+ for (const [modelName, store] of Array.from(this.stores)) {
233
+ const metadata = this.modelRegistry.getMetadata(modelName);
234
+ if (metadata?.loadStrategy === strategy) {
235
+ stores.push(store);
236
+ }
237
+ }
238
+ return stores;
239
+ }
240
+ /**
241
+ * Get models to load for bootstrapping
242
+ */
243
+ getModelsToLoad() {
244
+ const instant = [];
245
+ const lazy = [];
246
+ const partial = [];
247
+ for (const [modelName] of Array.from(this.stores)) {
248
+ const metadata = this.modelRegistry.getMetadata(modelName);
249
+ switch (metadata?.loadStrategy) {
250
+ case LoadStrategy.instant:
251
+ instant.push(modelName);
252
+ break;
253
+ case LoadStrategy.lazy:
254
+ lazy.push(modelName);
255
+ break;
256
+ case LoadStrategy.partial:
257
+ partial.push(modelName);
258
+ break;
259
+ // Skip explicitlyRequested and local
260
+ }
261
+ }
262
+ return { instant, lazy, partial };
263
+ }
264
+ /**
265
+ * Perform maintenance on all stores
266
+ */
267
+ async performMaintenance() {
268
+ getContext().logger.info('Performing maintenance on all stores');
269
+ const promises = Array.from(this.stores.values()).map((store) => store.performMaintenance());
270
+ await Promise.all(promises);
271
+ getContext().logger.info('Store maintenance completed');
272
+ }
273
+ /**
274
+ * Clear all stores
275
+ */
276
+ async clearAllStores() {
277
+ getContext().logger.warn('Clearing all stores');
278
+ const promises = Array.from(this.stores.values()).map((store) => store.clear());
279
+ await Promise.all(promises);
280
+ getContext().logger.info('All stores cleared');
281
+ }
282
+ /**
283
+ * Mark all stores as closing to prevent new operations
284
+ * Called before database connection is closed
285
+ */
286
+ markAllStoresAsClosing() {
287
+ getContext().logger.debug('Marking all stores as closing');
288
+ for (const store of this.stores.values()) {
289
+ store.markAsClosing();
290
+ }
291
+ // SyncActionStore is a standalone store (does NOT extend ObjectStore)
292
+ // and has no markAsClosing equivalent. The previous `(syncactionStore
293
+ // as any).markAsClosing?.()` was a silent no-op disguised as a real
294
+ // call — the optional chain swallowed the missing method. If
295
+ // closing-state coordination is needed for sync actions, add it
296
+ // explicitly to SyncActionStore rather than reintroducing the cast.
297
+ getContext().logger.debug('All stores marked as closing');
298
+ }
299
+ /**
300
+ * Get comprehensive statistics
301
+ */
302
+ async getComprehensiveStats() {
303
+ const storeDetails = [];
304
+ let totalRecords = 0;
305
+ let readyCount = 0;
306
+ for (const [modelName, store] of Array.from(this.stores)) {
307
+ const metadata = this.modelRegistry.getMetadata(modelName);
308
+ const storeName = modelName;
309
+ const ready = await store.checkIsReady();
310
+ const count = await store.count();
311
+ if (ready)
312
+ readyCount++;
313
+ totalRecords += count;
314
+ storeDetails.push({
315
+ modelName,
316
+ storeName,
317
+ strategy: metadata.loadStrategy,
318
+ ready,
319
+ count,
320
+ });
321
+ }
322
+ return {
323
+ totalStores: this.stores.size,
324
+ storeTypes: this.getStoreTypeDistribution(),
325
+ readiness: {
326
+ ready: readyCount,
327
+ notReady: this.stores.size - readyCount,
328
+ },
329
+ totalRecords,
330
+ storeDetails,
331
+ };
332
+ }
333
+ }
334
+ import { getContext } from '../context.js';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ViewRegistry — tracks active QueryViews per typename.
3
+ *
4
+ * When the ObjectPool mutates a model, it calls notifyAdded / notifyUpdated /
5
+ * notifyRemoved on the registry, which fans the event out to every active
6
+ * QueryView subscribed to that typename.
7
+ */
8
+ import { type Model } from '../Model.js';
9
+ import type { IncrementalView } from './query-utils.js';
10
+ export declare class ViewRegistry {
11
+ private views;
12
+ register(typename: string, view: IncrementalView): void;
13
+ unregister(typename: string, view: IncrementalView): void;
14
+ /** Called by ObjectPool after a model is added to the pool. */
15
+ notifyAdded(typename: string, model: Model): void;
16
+ /** Called by ObjectPool after a model is updated in the pool. */
17
+ notifyUpdated(typename: string, model: Model): void;
18
+ /** Called by ObjectPool after a model is removed from the pool. */
19
+ notifyRemoved(typename: string, modelId: string): void;
20
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * ViewRegistry — tracks active QueryViews per typename.
3
+ *
4
+ * When the ObjectPool mutates a model, it calls notifyAdded / notifyUpdated /
5
+ * notifyRemoved on the registry, which fans the event out to every active
6
+ * QueryView subscribed to that typename.
7
+ */
8
+ import { modelAsRow } from '../Model.js';
9
+ export class ViewRegistry {
10
+ views = new Map();
11
+ register(typename, view) {
12
+ let set = this.views.get(typename);
13
+ if (!set) {
14
+ set = new Set();
15
+ this.views.set(typename, set);
16
+ }
17
+ set.add(view);
18
+ }
19
+ unregister(typename, view) {
20
+ const set = this.views.get(typename);
21
+ if (!set)
22
+ return;
23
+ set.delete(view);
24
+ if (set.size === 0) {
25
+ this.views.delete(typename);
26
+ }
27
+ }
28
+ /** Called by ObjectPool after a model is added to the pool. */
29
+ notifyAdded(typename, model) {
30
+ const set = this.views.get(typename);
31
+ if (!set)
32
+ return;
33
+ for (const view of set) {
34
+ view.handleAdded(modelAsRow(model));
35
+ }
36
+ }
37
+ /** Called by ObjectPool after a model is updated in the pool. */
38
+ notifyUpdated(typename, model) {
39
+ const set = this.views.get(typename);
40
+ if (!set)
41
+ return;
42
+ for (const view of set) {
43
+ view.handleUpdated(modelAsRow(model));
44
+ }
45
+ }
46
+ /** Called by ObjectPool after a model is removed from the pool. */
47
+ notifyRemoved(typename, modelId) {
48
+ const set = this.views.get(typename);
49
+ if (!set)
50
+ return;
51
+ for (const view of set) {
52
+ view.handleRemoved(modelId);
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @ablo/sync-engine-internal/core — Framework extension
3
+ *
4
+ * Only imported by SyncedStore.ts and ApplicationStore.ts —
5
+ * the 2-3 files that extend or orchestrate the sync engine.
6
+ * Regular model files and components should NOT import from here.
7
+ */
8
+ export { BaseSyncedStore, type SyncStatus, type UserContext, type SyncedStoreConfig, type QueryResult, type SmartSyncOptions, type ModelConstructor, type ConcreteModelConstructor, BOOTSTRAP_CONFIG, } from '../BaseSyncedStore.js';
9
+ export { SyncClient, type RehydrationStats } from '../SyncClient.js';
10
+ export { Database, type BootstrapResult, type BootstrapRequirements } from '../Database.js';
11
+ export { ObjectPool, ModelScope } from '../ObjectPool.js';
12
+ export { Model } from '../Model.js';
13
+ export { LazyReferenceCollection, type LazyCollectionOptions } from '../LazyReferenceCollection.js';
14
+ export { postQuery, type PostQueryOptions } from '../query/client.js';
15
+ export { probeNetwork, type ProbeResult } from '../sync/NetworkProbe.js';
16
+ export { ConnectionManager, type ConnectionState, type ConnectionEvent, type ConnectionCallbacks, type ConnectionManagerOptions, } from '../sync/ConnectionManager.js';
17
+ export { ModelRegistry, getActiveRegistry, type ExtendedReferenceMetadata, type BackReferenceMetadata } from '../ModelRegistry.js';
18
+ export { computeFKDepthPriority, type InternalAbloOptions } from '../client/Ablo.js';
19
+ export { TransactionQueue } from '../transactions/TransactionQueue.js';
20
+ export { initSyncEngine, resetSyncEngine, isSyncEngineInitialized } from '../context.js';
21
+ export { noopLogger, noopObservability, noopAnalytics, browserOnlineStatus, defaultSessionErrorDetector, emptyConfig, type SyncEngineContext, } from '../SyncEngineContext.js';
22
+ export type { SyncEngineConfig, SyncLogger, SyncObservabilityProvider, SyncAnalytics, MutationExecutor, MutationDispatcher, SessionErrorDetector, OnlineStatusProvider, CommitResult, MutationOperation, BreadcrumbLevel, SyncBreadcrumbCategory, TransactionFailureDetails, BootstrapFailureDetails, WebSocketErrorDetails, RollbackDetails, SpanAttributes, } from '../interfaces/index.js';
23
+ export { SyncSessionError } from '../errors.js';
24
+ export { QueryProcessor } from './QueryProcessor.js';
25
+ export { QueryView, type QueryViewOptions } from './QueryView.js';
26
+ export { ViewRegistry } from './ViewRegistry.js';
27
+ export { ObjectStore } from '../stores/ObjectStore.js';
28
+ export { NetworkMonitor } from '../NetworkMonitor.js';
29
+ export { SyncWebSocket, type SyncDelta, type VersionVector, type BootstrapHint, type SyncGroupChangePayload, type BootstrapDataEvent, type PresenceUpdateEvent, type SyncWebSocketOptions, } from '../sync/SyncWebSocket.js';
30
+ export { BootstrapHelper, type BootstrapData, type BootstrapOptions, type BootstrapFetchResult } from '../sync/BootstrapHelper.js';
31
+ export { OfflineTransactionStore, offlineTxStore, Priority } from '../sync/OfflineTransactionStore.js';
32
+ export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
33
+ export type { PropertyMetadata, ReferenceMetadata, ModelMetadata, SyncAction, DeltaPacket, BootstrapMetadata, DatabaseMetadata, } from '../types/index.js';
34
+ export type { ModelData } from '../BaseSyncedStore.js';
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @ablo/sync-engine-internal/core — Framework extension
3
+ *
4
+ * Only imported by SyncedStore.ts and ApplicationStore.ts —
5
+ * the 2-3 files that extend or orchestrate the sync engine.
6
+ * Regular model files and components should NOT import from here.
7
+ */
8
+ // Base store class
9
+ export { BaseSyncedStore, BOOTSTRAP_CONFIG, } from '../BaseSyncedStore.js';
10
+ // Core infrastructure
11
+ export { SyncClient } from '../SyncClient.js';
12
+ export { Database } from '../Database.js';
13
+ export { ObjectPool, ModelScope } from '../ObjectPool.js';
14
+ export { Model } from '../Model.js';
15
+ export { LazyReferenceCollection } from '../LazyReferenceCollection.js';
16
+ // Undo runtime — `useUndoScope` hook from `@ablo/sync-engine/react` is
17
+ // the canonical access path. Type counterparts (`Ablo.Mutator.UndoScope`,
18
+ // `Ablo.Mutator.UndoEntry`, `Ablo.Mutator.InverseOp`) live on the main `Ablo`
19
+ // namespace. Direct class access (tests, non-React hosts) imports via
20
+ // the package's internal subpath.
21
+ // Lower-level network primitives — exposed here for the per-app demand
22
+ // loaders. The main barrel hides these so consumer code converges on
23
+ // `ablo.<model>.fetch(...)` for hydration. Loaders that haven't been
24
+ // migrated yet can keep importing from `/core`.
25
+ export { postQuery } from '../query/client.js';
26
+ export { probeNetwork } from '../sync/NetworkProbe.js';
27
+ export { ConnectionManager, } from '../sync/ConnectionManager.js';
28
+ export { ModelRegistry, getActiveRegistry } from '../ModelRegistry.js';
29
+ // FK-cycle / dependency-order helper — used by schema-aware test
30
+ // fixtures and scaffolding tools to compute commit ordering. Lives
31
+ // here because it traverses model relations and isn't part of the
32
+ // consumer-facing API.
33
+ export { computeFKDepthPriority } from '../client/Ablo.js';
34
+ export { TransactionQueue } from '../transactions/TransactionQueue.js';
35
+ // ── Provider-facing DI types (was the deleted `/config` subpath) ──
36
+ // Adapters that the consumer wires into `<AbloProvider>` props
37
+ // (logger, observability, mutation executor, session-error
38
+ // detector, etc.) implement these interfaces. Lives on `/core`
39
+ // because most consumers don't need them; only apps that wrap the
40
+ // provider with custom adapters reach for them.
41
+ export { initSyncEngine, resetSyncEngine, isSyncEngineInitialized } from '../context.js';
42
+ export { noopLogger, noopObservability, noopAnalytics, browserOnlineStatus, defaultSessionErrorDetector, emptyConfig, } from '../SyncEngineContext.js';
43
+ export { SyncSessionError } from '../errors.js';
44
+ export { QueryProcessor } from './QueryProcessor.js';
45
+ export { QueryView } from './QueryView.js';
46
+ export { ViewRegistry } from './ViewRegistry.js';
47
+ export { ObjectStore } from '../stores/ObjectStore.js';
48
+ export { NetworkMonitor } from '../NetworkMonitor.js';
49
+ // Sync layer
50
+ export { SyncWebSocket, } from '../sync/SyncWebSocket.js';
51
+ export { BootstrapHelper } from '../sync/BootstrapHelper.js';
52
+ // Offline transaction queue — moved out of the main barrel in the headless
53
+ // audit cleanup (see docs/headless-audit.md §4.1 Task 23). The class
54
+ // touches indexedDB + crypto.subtle and therefore cannot live on the main
55
+ // headless-clean import path. Framework-level consumers (the few files
56
+ // that orchestrate sync) import from /core explicitly.
57
+ export { OfflineTransactionStore, offlineTxStore, Priority } from '../sync/OfflineTransactionStore.js';
58
+ // Types used by framework-level code
59
+ export { PropertyType, LoadStrategy, MutationOperationType } from '../types/index.js';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Wrap `indexedDB.open()` with a timeout + loud `onblocked` surfacing so the
3
+ * request can never silently hang the app.
4
+ *
5
+ * The native `IDBOpenDBRequest` has a nasty failure mode: if another tab is
6
+ * holding an older schema version, the request fires `onblocked` and then
7
+ * waits forever for that tab to close the DB. Neither `onsuccess` nor
8
+ * `onerror` fires — the promise wrapping it never settles and every caller
9
+ * above sits indefinitely.
10
+ *
11
+ * This helper converts that into a real error after a bounded wait, which
12
+ * flows up through `engine.ready()` → `SyncEngineProvider.handleError()` →
13
+ * the error skeleton with a retry button. A visible error is strictly better
14
+ * than a forever-spinner the user can only escape by closing the tab.
15
+ */
16
+ export declare class IDBOpenTimeoutError extends Error {
17
+ readonly dbName: string;
18
+ readonly reason: 'blocked' | 'timeout';
19
+ constructor(dbName: string, reason: 'blocked' | 'timeout', message: string);
20
+ }
21
+ export interface OpenIDBOptions {
22
+ /** Called inside `onupgradeneeded` — mirrors `IDBOpenDBRequest.onupgradeneeded`. */
23
+ onUpgrade?: (request: IDBOpenDBRequest, event: IDBVersionChangeEvent) => void;
24
+ /** Max milliseconds to wait for the open request to resolve. Default 10_000. */
25
+ timeoutMs?: number;
26
+ }
27
+ export declare function openIDBWithTimeout(name: string, version: number | undefined, options?: OpenIDBOptions): Promise<IDBDatabase>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Wrap `indexedDB.open()` with a timeout + loud `onblocked` surfacing so the
3
+ * request can never silently hang the app.
4
+ *
5
+ * The native `IDBOpenDBRequest` has a nasty failure mode: if another tab is
6
+ * holding an older schema version, the request fires `onblocked` and then
7
+ * waits forever for that tab to close the DB. Neither `onsuccess` nor
8
+ * `onerror` fires — the promise wrapping it never settles and every caller
9
+ * above sits indefinitely.
10
+ *
11
+ * This helper converts that into a real error after a bounded wait, which
12
+ * flows up through `engine.ready()` → `SyncEngineProvider.handleError()` →
13
+ * the error skeleton with a retry button. A visible error is strictly better
14
+ * than a forever-spinner the user can only escape by closing the tab.
15
+ */
16
+ export class IDBOpenTimeoutError extends Error {
17
+ dbName;
18
+ reason;
19
+ constructor(dbName, reason, message) {
20
+ super(message);
21
+ this.dbName = dbName;
22
+ this.reason = reason;
23
+ this.name = 'IDBOpenTimeoutError';
24
+ }
25
+ }
26
+ export function openIDBWithTimeout(name, version, options = {}) {
27
+ const timeoutMs = options.timeoutMs ?? 10_000;
28
+ return new Promise((resolve, reject) => {
29
+ const request = version === undefined
30
+ ? indexedDB.open(name)
31
+ : indexedDB.open(name, version);
32
+ let settled = false;
33
+ const settle = (fn) => {
34
+ if (settled)
35
+ return;
36
+ settled = true;
37
+ clearTimeout(timer);
38
+ fn();
39
+ };
40
+ if (options.onUpgrade) {
41
+ request.onupgradeneeded = (event) => {
42
+ options.onUpgrade(request, event);
43
+ };
44
+ }
45
+ request.onsuccess = () => settle(() => resolve(request.result));
46
+ request.onerror = () => settle(() => reject(request.error));
47
+ // The critical handler: another tab is blocking us. Native API leaves
48
+ // the request pending indefinitely; we fail fast with a clear error so
49
+ // the UI can tell the user to close other tabs.
50
+ request.onblocked = () => {
51
+ settle(() => reject(new IDBOpenTimeoutError(name, 'blocked', `IndexedDB \"${name}\" open blocked — another tab is holding an ` +
52
+ `older version. Close other Ablo tabs and reload.`)));
53
+ };
54
+ // Catch-all timeout: even without `onblocked`, some browsers in some
55
+ // storage states hang without firing any event. Bounded wait →
56
+ // deterministic error.
57
+ const timer = setTimeout(() => {
58
+ settle(() => reject(new IDBOpenTimeoutError(name, 'timeout', `IndexedDB \"${name}\" open did not resolve within ${timeoutMs}ms. ` +
59
+ `Storage may be in a bad state — clearing site data and reloading ` +
60
+ `usually fixes this.`)));
61
+ }, timeoutMs);
62
+ });
63
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * query-utils — Pure query helpers shared between QueryView (MobX) and
3
+ * AgentQueryView (headless). One source of truth for sort, filter, and
4
+ * binary insertion logic.
5
+ *
6
+ * No MobX, no ObjectPool, no Model — just arrays and values.
7
+ */
8
+ /**
9
+ * The incremental-update contract that both QueryView and
10
+ * AgentQueryView satisfy. Their respective registries (ViewRegistry,
11
+ * AgentViewRegistry) store views as this base type so they can
12
+ * dispatch to many views with different `T` parameters from one Set
13
+ * — `View<T>` is invariant in T, so without this shared base the
14
+ * registries would have to widen via `unknown as View<Record<...>>`
15
+ * at every register/unregister/notify call.
16
+ */
17
+ export interface IncrementalView {
18
+ handleAdded(entity: Record<string, unknown>): void;
19
+ handleUpdated(entity: Record<string, unknown>): void;
20
+ handleRemoved(id: string): void;
21
+ }
22
+ /** Compare two values for sorting, null-safe. Returns -1 | 0 | 1. */
23
+ export declare function compareValues(a: unknown, b: unknown, dir: 1 | -1): number;
24
+ /**
25
+ * Binary search for the correct insertion index in a sorted array.
26
+ * Returns the index at which `item` should be inserted.
27
+ */
28
+ export declare function binaryInsertionIndex<T>(arr: ArrayLike<T>, item: T, sortKey: string, dir: 1 | -1): number;
29
+ /**
30
+ * Check whether an entity matches a declarative `where` clause.
31
+ * Every key in `where` must exactly match the entity's value.
32
+ */
33
+ export declare function matchesWhere<T extends Record<string, unknown>>(entity: T, where: Partial<T>): boolean;
34
+ /**
35
+ * Find the index of an entity by id in an array. Returns -1 if not found.
36
+ */
37
+ export declare function findIndexById<T extends Record<string, unknown>>(arr: ArrayLike<T>, id: string): number;