@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,103 @@
1
+ /**
2
+ * Linear Sync Engine - Object Store Base Class
3
+ *
4
+ * Abstract base class for all store implementations.
5
+ * Provides the interface for storing and retrieving models from IndexedDB.
6
+ * Uses native IndexedDB for maximum performance (no wrapper overhead).
7
+ */
8
+ import { ModelMetadata } from '../types/index.js';
9
+ import type { ObjectStoreContract } from './ObjectStoreContract.js';
10
+ /**
11
+ * ObjectStore - IDB-backed model storage.
12
+ *
13
+ * Implements {@link ObjectStoreContract}, the shared surface that
14
+ * `InMemoryObjectStore` also satisfies. Centralizing the contract
15
+ * means callers can hold either implementation behind one type and
16
+ * a future drift between the two trips a typecheck error here.
17
+ *
18
+ * Uses native IndexedDB API for Linear-level performance.
19
+ */
20
+ export declare class ObjectStore implements ObjectStoreContract {
21
+ protected db: IDBDatabase;
22
+ protected modelName: string;
23
+ protected storeName: string;
24
+ protected metadata: ModelMetadata;
25
+ private isClosing;
26
+ constructor(db: IDBDatabase, modelName: string, storeName: string, metadata: ModelMetadata);
27
+ /**
28
+ * Mark this store as closing to prevent new operations
29
+ */
30
+ markAsClosing(): void;
31
+ /**
32
+ * Check if database is available for operations
33
+ */
34
+ private checkDatabaseAvailable;
35
+ /**
36
+ * Store a model in IndexedDB.
37
+ * Matches the {@link InMemoryObjectStore.put} signature so both
38
+ * stores satisfy the same caller-visible contract.
39
+ */
40
+ put(data: Record<string, unknown>): Promise<void>;
41
+ /**
42
+ * Get a model by ID
43
+ */
44
+ get(id: string): Promise<Record<string, unknown> | undefined>;
45
+ /**
46
+ * Batch get multiple models by IDs in a single IDB transaction.
47
+ * Much faster than N sequential get() calls (1 transaction vs N).
48
+ */
49
+ getMany(ids: string[]): Promise<Map<string, Record<string, unknown>>>;
50
+ /**
51
+ * Get all models
52
+ */
53
+ getAll(): Promise<Record<string, unknown>[]>;
54
+ /**
55
+ * Delete a model by ID
56
+ */
57
+ delete(id: string): Promise<void>;
58
+ /**
59
+ * Check if store is ready (has data)
60
+ */
61
+ checkIsReady(): Promise<boolean>;
62
+ /**
63
+ * Clear all data in store
64
+ */
65
+ clear(): Promise<void>;
66
+ /**
67
+ * Get count of models in store
68
+ */
69
+ count(): Promise<number>;
70
+ /**
71
+ * Get models by indexed property
72
+ */
73
+ getAllFromIndex(indexName: string, value: IDBValidKey): Promise<Record<string, unknown>[]>;
74
+ /**
75
+ * Get models by indexed key (supports compound keys)
76
+ */
77
+ getAllForIndexedKey(indexedKey: string, keyValue: IDBValidKey): Promise<Record<string, unknown>[]>;
78
+ /**
79
+ * Get first model matching index
80
+ */
81
+ getFromIndex(indexName: string, value: IDBValidKey): Promise<Record<string, unknown> | undefined>;
82
+ /**
83
+ * Get count from index
84
+ */
85
+ countFromIndex(indexName: string, value: IDBValidKey): Promise<number>;
86
+ /**
87
+ * Check if any models exist for index value
88
+ */
89
+ hasModelsForIndex(indexName: string, value: IDBValidKey): Promise<boolean>;
90
+ /**
91
+ * Get store statistics
92
+ */
93
+ getStats(): Promise<{
94
+ count: number;
95
+ ready: boolean;
96
+ loadStrategy: string;
97
+ indexes: string[];
98
+ }>;
99
+ /**
100
+ * Perform maintenance (override in subclasses for specific needs)
101
+ */
102
+ performMaintenance(): Promise<void>;
103
+ }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Linear Sync Engine - Object Store Base Class
3
+ *
4
+ * Abstract base class for all store implementations.
5
+ * Provides the interface for storing and retrieving models from IndexedDB.
6
+ * Uses native IndexedDB for maximum performance (no wrapper overhead).
7
+ */
8
+ /**
9
+ * ObjectStore - IDB-backed model storage.
10
+ *
11
+ * Implements {@link ObjectStoreContract}, the shared surface that
12
+ * `InMemoryObjectStore` also satisfies. Centralizing the contract
13
+ * means callers can hold either implementation behind one type and
14
+ * a future drift between the two trips a typecheck error here.
15
+ *
16
+ * Uses native IndexedDB API for Linear-level performance.
17
+ */
18
+ export class ObjectStore {
19
+ db;
20
+ modelName;
21
+ storeName;
22
+ metadata;
23
+ isClosing = false;
24
+ constructor(db, modelName, storeName, metadata) {
25
+ this.db = db;
26
+ this.modelName = modelName;
27
+ this.storeName = storeName;
28
+ this.metadata = metadata;
29
+ }
30
+ /**
31
+ * Mark this store as closing to prevent new operations
32
+ */
33
+ markAsClosing() {
34
+ this.isClosing = true;
35
+ }
36
+ /**
37
+ * Check if database is available for operations
38
+ */
39
+ checkDatabaseAvailable() {
40
+ if (this.isClosing) {
41
+ return false;
42
+ }
43
+ // Check if the database connection is still open
44
+ // In IndexedDB, there's no direct way to check if a connection is open,
45
+ // but we can check if the database object is still valid
46
+ try {
47
+ // Accessing objectStoreNames will throw if the database is closed
48
+ const _ = this.db.objectStoreNames;
49
+ return true;
50
+ }
51
+ catch (error) {
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Store a model in IndexedDB.
57
+ * Matches the {@link InMemoryObjectStore.put} signature so both
58
+ * stores satisfy the same caller-visible contract.
59
+ */
60
+ async put(data) {
61
+ if (!this.checkDatabaseAvailable()) {
62
+ // Surface an explicit error so upstream does not assume success
63
+ return Promise.reject(new Error('IndexedDB not available (closing or invalid)'));
64
+ }
65
+ return new Promise((resolve, reject) => {
66
+ try {
67
+ // Use relaxed durability for ~16x write performance (safe with optimistic sync)
68
+ const tx = this.db.transaction([this.storeName], 'readwrite', {
69
+ durability: 'relaxed',
70
+ });
71
+ const store = tx.objectStore(this.storeName);
72
+ const request = store.put(data);
73
+ tx.oncomplete = () => resolve();
74
+ tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction error'));
75
+ request.onerror = () => reject(request.error || new Error('IndexedDB request error'));
76
+ }
77
+ catch (error) {
78
+ // Propagate failure so callers do not continue with inconsistent state
79
+ reject(error instanceof Error ? error : new Error(String(error)));
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Get a model by ID
85
+ */
86
+ async get(id) {
87
+ if (!this.checkDatabaseAvailable()) {
88
+ return Promise.resolve(undefined);
89
+ }
90
+ return new Promise((resolve, reject) => {
91
+ try {
92
+ const tx = this.db.transaction([this.storeName], 'readonly');
93
+ const store = tx.objectStore(this.storeName);
94
+ const request = store.get(id);
95
+ request.onsuccess = () => resolve(request.result);
96
+ request.onerror = () => reject(request.error);
97
+ }
98
+ catch (error) {
99
+ resolve(undefined);
100
+ }
101
+ });
102
+ }
103
+ /**
104
+ * Batch get multiple models by IDs in a single IDB transaction.
105
+ * Much faster than N sequential get() calls (1 transaction vs N).
106
+ */
107
+ async getMany(ids) {
108
+ const results = new Map();
109
+ if (ids.length === 0 || !this.checkDatabaseAvailable()) {
110
+ return results;
111
+ }
112
+ return new Promise((resolve, reject) => {
113
+ try {
114
+ const tx = this.db.transaction([this.storeName], 'readonly');
115
+ const store = tx.objectStore(this.storeName);
116
+ let completed = 0;
117
+ for (const id of ids) {
118
+ const request = store.get(id);
119
+ request.onsuccess = () => {
120
+ if (request.result) {
121
+ results.set(id, request.result);
122
+ }
123
+ completed++;
124
+ if (completed === ids.length) {
125
+ resolve(results);
126
+ }
127
+ };
128
+ request.onerror = () => {
129
+ completed++;
130
+ if (completed === ids.length) {
131
+ resolve(results);
132
+ }
133
+ };
134
+ }
135
+ tx.onerror = () => reject(tx.error);
136
+ }
137
+ catch (error) {
138
+ resolve(results);
139
+ }
140
+ });
141
+ }
142
+ /**
143
+ * Get all models
144
+ */
145
+ async getAll() {
146
+ if (!this.checkDatabaseAvailable()) {
147
+ return Promise.resolve([]);
148
+ }
149
+ return new Promise((resolve, reject) => {
150
+ try {
151
+ const tx = this.db.transaction([this.storeName], 'readonly');
152
+ const store = tx.objectStore(this.storeName);
153
+ const request = store.getAll();
154
+ request.onsuccess = () => resolve(request.result || []);
155
+ request.onerror = () => reject(request.error);
156
+ }
157
+ catch (error) {
158
+ resolve([]);
159
+ }
160
+ });
161
+ }
162
+ /**
163
+ * Delete a model by ID
164
+ */
165
+ async delete(id) {
166
+ if (!this.checkDatabaseAvailable()) {
167
+ return Promise.reject(new Error('IndexedDB not available (closing or invalid)'));
168
+ }
169
+ return new Promise((resolve, reject) => {
170
+ try {
171
+ // Use relaxed durability for ~16x write performance (safe with optimistic sync)
172
+ const tx = this.db.transaction([this.storeName], 'readwrite', {
173
+ durability: 'relaxed',
174
+ });
175
+ const store = tx.objectStore(this.storeName);
176
+ const request = store.delete(id);
177
+ tx.oncomplete = () => resolve();
178
+ tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction error'));
179
+ request.onerror = () => reject(request.error || new Error('IndexedDB request error'));
180
+ }
181
+ catch (error) {
182
+ reject(error instanceof Error ? error : new Error(String(error)));
183
+ }
184
+ });
185
+ }
186
+ /**
187
+ * Check if store is ready (has data)
188
+ */
189
+ async checkIsReady() {
190
+ try {
191
+ await this.count();
192
+ return true;
193
+ }
194
+ catch {
195
+ return false;
196
+ }
197
+ }
198
+ /**
199
+ * Clear all data in store
200
+ */
201
+ async clear() {
202
+ if (!this.checkDatabaseAvailable()) {
203
+ return Promise.reject(new Error('IndexedDB not available (closing or invalid)'));
204
+ }
205
+ return new Promise((resolve, reject) => {
206
+ try {
207
+ // Use relaxed durability for ~16x write performance (safe with optimistic sync)
208
+ const tx = this.db.transaction([this.storeName], 'readwrite', {
209
+ durability: 'relaxed',
210
+ });
211
+ const store = tx.objectStore(this.storeName);
212
+ const request = store.clear();
213
+ tx.oncomplete = () => resolve();
214
+ tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction error'));
215
+ request.onerror = () => reject(request.error || new Error('IndexedDB request error'));
216
+ }
217
+ catch (error) {
218
+ reject(error instanceof Error ? error : new Error(String(error)));
219
+ }
220
+ });
221
+ }
222
+ /**
223
+ * Get count of models in store
224
+ */
225
+ async count() {
226
+ if (!this.checkDatabaseAvailable()) {
227
+ return Promise.resolve(0);
228
+ }
229
+ return new Promise((resolve, reject) => {
230
+ try {
231
+ const tx = this.db.transaction([this.storeName], 'readonly');
232
+ const store = tx.objectStore(this.storeName);
233
+ const request = store.count();
234
+ request.onsuccess = () => resolve(request.result);
235
+ request.onerror = () => reject(request.error);
236
+ }
237
+ catch (error) {
238
+ resolve(0);
239
+ }
240
+ });
241
+ }
242
+ /**
243
+ * Get models by indexed property
244
+ */
245
+ async getAllFromIndex(indexName, value) {
246
+ if (!this.checkDatabaseAvailable()) {
247
+ return Promise.resolve([]);
248
+ }
249
+ return new Promise((resolve, reject) => {
250
+ try {
251
+ const tx = this.db.transaction([this.storeName], 'readonly');
252
+ const store = tx.objectStore(this.storeName);
253
+ const index = store.index(indexName);
254
+ const request = index.getAll(value);
255
+ request.onsuccess = () => resolve(request.result);
256
+ request.onerror = () => reject(request.error);
257
+ }
258
+ catch (error) {
259
+ resolve([]);
260
+ }
261
+ });
262
+ }
263
+ /**
264
+ * Get models by indexed key (supports compound keys)
265
+ */
266
+ async getAllForIndexedKey(indexedKey, keyValue) {
267
+ if (!this.checkDatabaseAvailable()) {
268
+ return Promise.resolve([]);
269
+ }
270
+ // For simple index
271
+ if (!indexedKey.includes('.')) {
272
+ return this.getAllFromIndex(indexedKey, keyValue);
273
+ }
274
+ // For compound index (e.g., "teamId.status")
275
+ return new Promise((resolve, reject) => {
276
+ try {
277
+ const tx = this.db.transaction([this.storeName], 'readonly');
278
+ const store = tx.objectStore(this.storeName);
279
+ const request = store.getAll();
280
+ request.onsuccess = () => {
281
+ const allRecords = request.result;
282
+ // Filter in memory for compound keys
283
+ const keyParts = indexedKey.split('.');
284
+ const filtered = allRecords.filter((record) => {
285
+ let value = record;
286
+ for (const part of keyParts) {
287
+ value = value?.[part];
288
+ if (value === undefined)
289
+ return false;
290
+ }
291
+ return value === keyValue;
292
+ });
293
+ resolve(filtered);
294
+ };
295
+ request.onerror = () => reject(request.error);
296
+ }
297
+ catch (error) {
298
+ resolve([]);
299
+ }
300
+ });
301
+ }
302
+ /**
303
+ * Get first model matching index
304
+ */
305
+ async getFromIndex(indexName, value) {
306
+ if (!this.checkDatabaseAvailable()) {
307
+ return Promise.resolve(undefined);
308
+ }
309
+ return new Promise((resolve, reject) => {
310
+ try {
311
+ const tx = this.db.transaction([this.storeName], 'readonly');
312
+ const store = tx.objectStore(this.storeName);
313
+ const index = store.index(indexName);
314
+ const request = index.get(value);
315
+ request.onsuccess = () => resolve(request.result);
316
+ request.onerror = () => reject(request.error);
317
+ }
318
+ catch (error) {
319
+ resolve(undefined);
320
+ }
321
+ });
322
+ }
323
+ /**
324
+ * Get count from index
325
+ */
326
+ async countFromIndex(indexName, value) {
327
+ if (!this.checkDatabaseAvailable()) {
328
+ return Promise.resolve(0);
329
+ }
330
+ return new Promise((resolve, reject) => {
331
+ try {
332
+ const tx = this.db.transaction([this.storeName], 'readonly');
333
+ const store = tx.objectStore(this.storeName);
334
+ const index = store.index(indexName);
335
+ const request = index.count(value);
336
+ request.onsuccess = () => resolve(request.result);
337
+ request.onerror = () => reject(request.error);
338
+ }
339
+ catch (error) {
340
+ resolve(0);
341
+ }
342
+ });
343
+ }
344
+ /**
345
+ * Check if any models exist for index value
346
+ */
347
+ async hasModelsForIndex(indexName, value) {
348
+ const count = await this.countFromIndex(indexName, value);
349
+ return count > 0;
350
+ }
351
+ /**
352
+ * Get store statistics
353
+ */
354
+ async getStats() {
355
+ const tx = this.db.transaction([this.storeName], 'readonly');
356
+ const store = tx.objectStore(this.storeName);
357
+ return {
358
+ count: await this.count(),
359
+ ready: await this.checkIsReady(),
360
+ loadStrategy: this.metadata.loadStrategy,
361
+ indexes: store.indexNames ? Array.from(store.indexNames) : [],
362
+ };
363
+ }
364
+ /**
365
+ * Perform maintenance (override in subclasses for specific needs)
366
+ */
367
+ async performMaintenance() {
368
+ // Default: no maintenance needed
369
+ // Subclasses can override for compaction, cleanup, etc.
370
+ }
371
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared contract for record-shaped object stores.
3
+ *
4
+ * The SDK has two implementations:
5
+ * - {@link ObjectStore} — IndexedDB-backed (browser persistence)
6
+ * - {@link InMemoryObjectStore} — Map-backed (tests, SSR fallback)
7
+ *
8
+ * Both expose the same async surface: `put` / `get` / `getAll` /
9
+ * `delete` / `getAllFromIndex` / `clear` / `markAsClosing`.
10
+ * Callers depend on this interface so they don't have to
11
+ * branch on which concrete class they got from `Database.getStore` —
12
+ * the bootstrap, hydration, transaction-persistence, and reconciler
13
+ * paths all consume the contract.
14
+ *
15
+ * Centralizing the types here means a future drift between the two
16
+ * stores trips a typecheck error at the implementor, not silently in
17
+ * a caller. This replaced ad-hoc `as unknown as ReturnType<...>`
18
+ * casts in `Database.ts` that bridged the two classes.
19
+ */
20
+ export interface ObjectStoreContract {
21
+ /** Insert or update a record. The record must carry an `id` field. */
22
+ put(data: Record<string, unknown>): Promise<void>;
23
+ /** Look up a record by id. */
24
+ get(id: string): Promise<Record<string, unknown> | undefined>;
25
+ /** Read every record currently in the store. */
26
+ getAll(): Promise<Record<string, unknown>[]>;
27
+ /** Delete a record by id. No-op if absent. */
28
+ delete(id: string): Promise<void>;
29
+ /** Read every record matching an indexed value. */
30
+ getAllFromIndex(indexName: string, value: IDBValidKey): Promise<Record<string, unknown>[]>;
31
+ /** Remove every record. */
32
+ clear(): Promise<void>;
33
+ /**
34
+ * Mark the store as closing so subsequent `put`/`get` calls
35
+ * short-circuit to a rejection rather than racing the underlying
36
+ * IDB connection close. No-op for in-memory stores.
37
+ */
38
+ markAsClosing(): void;
39
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,101 @@
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
+ import { SyncAction } from '../types/index.js';
8
+ /**
9
+ * SyncActionStore - Manages sync actions (deltas)
10
+ *
11
+ * Features:
12
+ * - Stores sync actions by ID for replay
13
+ * - Tracks applied vs pending actions
14
+ * - Enables rewind/replay for conflict resolution
15
+ * - Maintains sync watermark
16
+ */
17
+ export declare class SyncActionStore {
18
+ private db;
19
+ private storeName;
20
+ private lastAppliedSyncId;
21
+ private pendingActions;
22
+ constructor(db: IDBDatabase);
23
+ /**
24
+ * Initialize store (create if needed)
25
+ */
26
+ initialize(): Promise<void>;
27
+ /**
28
+ * Store a sync action
29
+ */
30
+ storeSyncAction(action: SyncAction): Promise<void>;
31
+ /**
32
+ * Store multiple sync actions
33
+ */
34
+ storeSyncActions(actions: SyncAction[]): Promise<void>;
35
+ /**
36
+ * Get sync action by ID
37
+ */
38
+ getSyncAction(id: number): Promise<SyncAction | undefined>;
39
+ /**
40
+ * Get sync actions in range
41
+ */
42
+ getSyncActionsInRange(startId: number, endId: number): Promise<SyncAction[]>;
43
+ /**
44
+ * Get pending sync actions (not yet applied)
45
+ */
46
+ getPendingSyncActions(): Promise<SyncAction[]>;
47
+ /**
48
+ * Mark sync action as applied
49
+ */
50
+ markAsApplied(syncId: number): Promise<void>;
51
+ /**
52
+ * Mark multiple actions as applied
53
+ */
54
+ markManyAsApplied(syncIds: number[]): Promise<void>;
55
+ /**
56
+ * Get last applied sync ID
57
+ */
58
+ getLastAppliedSyncId(): number;
59
+ /**
60
+ * Check if we have a gap in sync IDs
61
+ */
62
+ hasGap(fromId: number, toId: number): Promise<boolean>;
63
+ /**
64
+ * Get missing sync IDs in range
65
+ */
66
+ getMissingSyncIds(fromId: number, toId: number): Promise<number[]>;
67
+ /**
68
+ * Clean up old sync actions
69
+ */
70
+ cleanup(keepDays?: number): Promise<number>;
71
+ /**
72
+ * Clear all sync actions
73
+ */
74
+ clear(): Promise<void>;
75
+ /**
76
+ * Get statistics
77
+ */
78
+ getStats(): Promise<{
79
+ total: number;
80
+ applied: number;
81
+ pending: number;
82
+ lastAppliedId: number;
83
+ oldestAction: Date | null;
84
+ newestAction: Date | null;
85
+ }>;
86
+ /**
87
+ * Update last sync ID in metadata
88
+ */
89
+ private updateLastSyncId;
90
+ /**
91
+ * Get metadata. Shape mirrors what's written by the database manager
92
+ * — currently only `lastSyncId` is consumed here (in `initialize`),
93
+ * so the return type is narrowed to that read surface. Returns
94
+ * `undefined` when the row hasn't been written yet (fresh DB).
95
+ */
96
+ private getMetadata;
97
+ /**
98
+ * Rewind to a specific sync ID (for conflict resolution)
99
+ */
100
+ rewindTo(syncId: number): Promise<SyncAction[]>;
101
+ }