@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,663 @@
1
+ /**
2
+ * SyncWebSocket - Manages WebSocket connection to Go sync engine
3
+ *
4
+ * Handles:
5
+ * - WebSocket lifecycle (connect, reconnect, disconnect)
6
+ * - Delta reception and processing
7
+ * - Multi-tab support
8
+ * - Automatic reconnection with exponential backoff
9
+ */
10
+ import { EventEmitter } from 'events';
11
+ /** JSON model data from the sync engine — may arrive as a pre-parsed object or a JSON string. */
12
+ type SyncDeltaPayload = Record<string, unknown> | string | null;
13
+ export interface SyncDelta {
14
+ id: number;
15
+ /**
16
+ * Delta action type — full Linear-compatible vocabulary.
17
+ *
18
+ * Core CRUD:
19
+ * I — Insert
20
+ * U — Update
21
+ * D — Delete (hard)
22
+ * A — Archive (soft delete)
23
+ * V — Unarchive (reVive)
24
+ *
25
+ * Permission / access control:
26
+ * C — Covering: client gained permission to see an existing entity
27
+ * (treated as insert by the client — see handleCovering path).
28
+ * G — GroupAdded: recipient was added to a sync group. Paired with
29
+ * subsequent 'C' deltas for each newly-visible entity.
30
+ * S — GroupRemoved: recipient lost access to a sync group. Client
31
+ * purges affected entities from its local store.
32
+ */
33
+ actionType: 'I' | 'U' | 'D' | 'A' | 'V' | 'C' | 'G' | 'S';
34
+ modelName: string;
35
+ modelId: string;
36
+ data: SyncDeltaPayload;
37
+ previousData?: SyncDeltaPayload;
38
+ metadata?: SyncDeltaPayload;
39
+ syncGroups: string[];
40
+ createdBy?: string;
41
+ transactionId?: string;
42
+ clientMutationId?: string;
43
+ createdAt: string;
44
+ }
45
+ /**
46
+ * Payload for legacy actionType 'G' deltas emitted by EmitGroupChange.
47
+ * Carries both added and removed groups in one delta, forces full re-bootstrap.
48
+ */
49
+ export interface SyncGroupChangePayload {
50
+ removedGroups: string[];
51
+ addedGroups: string[];
52
+ }
53
+ /**
54
+ * Payload for incremental actionType 'G' deltas emitted by EmitGroupAdded.
55
+ * Signals that the recipient has joined a single sync group; subsequent
56
+ * 'C' (Covering) deltas will deliver the newly-visible entities. No
57
+ * re-bootstrap required.
58
+ */
59
+ export interface GroupAddedPayload {
60
+ group: string;
61
+ userId: string;
62
+ }
63
+ /**
64
+ * Payload for actionType 'S' deltas emitted by EmitGroupRemoved.
65
+ * Signals that the recipient has lost access to a sync group. The client
66
+ * purges affected local entities and updates its subscription metadata.
67
+ */
68
+ export interface GroupRemovedPayload {
69
+ group: string;
70
+ userId: string;
71
+ }
72
+ export interface VersionVector {
73
+ tasks: number;
74
+ projects: number;
75
+ users: number;
76
+ events: number;
77
+ inboxitems: number;
78
+ teams: number;
79
+ assignments: number;
80
+ comments: number;
81
+ threads: number;
82
+ [entityType: string]: number;
83
+ }
84
+ export interface SyncCapabilities {
85
+ partialBootstrap?: boolean;
86
+ compressedDeltas?: boolean;
87
+ streamingBootstrap?: boolean;
88
+ batchedDeltas?: boolean;
89
+ }
90
+ export interface SyncWebSocketOptions {
91
+ /** Base HTTP URL of the sync server */
92
+ baseUrl?: string;
93
+ url?: string;
94
+ userId: string;
95
+ organizationId: string;
96
+ lastSyncId?: number;
97
+ syncGroups?: string[];
98
+ versions?: VersionVector;
99
+ capabilities?: SyncCapabilities;
100
+ reconnectDelay?: number;
101
+ maxReconnectDelay?: number;
102
+ /**
103
+ * Collaboration event type keys to listen for (e.g., ['sheet:selection', 'slide:cursor']).
104
+ * Wire messages with matching types (underscore format) will be emitted as events.
105
+ */
106
+ collaborationEvents?: string[];
107
+ /**
108
+ * Participant kind to declare on the WS upgrade. Defaults to `'user'`
109
+ * (session-auth, web app). Agent runtimes (Node workers) pass
110
+ * `'agent'` so the server's `agentTokenProvider`
111
+ * routes them through capability-token verification instead of
112
+ * session auth. The server reads this as the `kind` query param.
113
+ */
114
+ kind?: 'user' | 'agent' | 'system';
115
+ /**
116
+ * Biscuit capability bearer token. When set, sent as
117
+ * `?authorization=Bearer+<token>` on the WS upgrade — query-param
118
+ * form so it works in both Node (no header support) and browsers.
119
+ * The server's auth path accepts either form. Required for
120
+ * `kind: 'agent'`; ignored for `kind: 'user'`.
121
+ */
122
+ capabilityToken?: string;
123
+ }
124
+ /**
125
+ * Bootstrap hint from server indicating full or partial bootstrap is needed.
126
+ * Properties are optional since server payload structure may vary.
127
+ */
128
+ export interface BootstrapHint {
129
+ tables?: string[];
130
+ reason?: 'too_far_behind' | 'too_many_deltas' | 'missing_entities';
131
+ staleTables?: string[];
132
+ totalDeltaCount?: number;
133
+ }
134
+ /** Bootstrap data event payload */
135
+ export interface BootstrapDataEvent {
136
+ entityType: string;
137
+ data: unknown;
138
+ isComplete: boolean;
139
+ cursor?: string;
140
+ }
141
+ /**
142
+ * Presence update event payload — mirrors the wire frame's `payload`
143
+ * field (apps/sync-server/src/hub/types.ts PresenceUpdateMessage).
144
+ *
145
+ * Every consumer (web entity-presence cache, PresenceStream,
146
+ * agent-runtime presence reducer) reads its own subset; this type is
147
+ * the union of what the server actually sends. Stripping fields at
148
+ * this layer (the prior bug) silently broke rich-presence consumers
149
+ * that needed `kind`, `activity`, `isAgent` to dispatch correctly.
150
+ */
151
+ export interface PresenceUpdateEvent {
152
+ /** Server-stamped transition: 'enter' on join + roster snapshot,
153
+ * 'update' on activity change, 'leave' on disconnect. */
154
+ kind?: 'enter' | 'update' | 'leave';
155
+ userId: string;
156
+ status: string;
157
+ syncGroups?: string[];
158
+ activity?: {
159
+ entityType: string;
160
+ entityId: string;
161
+ path?: string;
162
+ range?: {
163
+ startLine: number;
164
+ endLine: number;
165
+ startColumn?: number;
166
+ endColumn?: number;
167
+ };
168
+ field?: string;
169
+ meta?: Record<string, unknown>;
170
+ action: string;
171
+ detail?: string;
172
+ };
173
+ /** Server-derived from the connection's userId prefix. Clients must
174
+ * not self-declare — server is the source of truth. */
175
+ isAgent?: boolean;
176
+ timestamp?: number;
177
+ /** Server stamps every presence frame with this participant's open
178
+ * intent claims so peers see them without a separate channel. Wire
179
+ * shape mirrors `apps/sync-server/src/hub/types.ts IntentClaim`. */
180
+ activeIntents?: Array<{
181
+ intentId: string;
182
+ entityType: string;
183
+ entityId: string;
184
+ path?: string;
185
+ range?: {
186
+ startLine: number;
187
+ endLine: number;
188
+ startColumn?: number;
189
+ endColumn?: number;
190
+ };
191
+ action: string;
192
+ field?: string;
193
+ meta?: Record<string, unknown>;
194
+ declaredAt: number;
195
+ expiresAt: number;
196
+ }>;
197
+ localTime?: string;
198
+ type?: string;
199
+ timezone?: string;
200
+ socketId?: string;
201
+ }
202
+ /**
203
+ * Core event map — transport-level events that every SyncWebSocket emits.
204
+ * SDK consumers extend this with app-specific collaboration events.
205
+ */
206
+ export interface CoreSyncEventMap {
207
+ connected: [];
208
+ disconnected: [CloseEvent];
209
+ reconnecting: [{
210
+ attempt: number;
211
+ delay: number;
212
+ }];
213
+ delta: [SyncDelta];
214
+ delta_batch: [SyncDelta[]];
215
+ bootstrap_required: [BootstrapHint];
216
+ bootstrap_data: [BootstrapDataEvent];
217
+ presence_update: [PresenceUpdateEvent];
218
+ error: [Error];
219
+ session_error: [Error];
220
+ /**
221
+ * The WebSocket `onclose` fired before `onopen` — the handshake itself
222
+ * failed. The browser cannot expose the HTTP status (it shows as code
223
+ * 1006 with no reason), so the consumer should run an authenticated
224
+ * HTTP probe to distinguish auth failure (session expired) from a
225
+ * generic network issue.
226
+ */
227
+ handshake_failed: [CloseEvent];
228
+ reconnect_failed: [{
229
+ attempts: number;
230
+ }];
231
+ /**
232
+ * Server-initiated notification that a previously-active claim's
233
+ * TTL has expired. Consumers (e.g., the participant SDK) re-mint
234
+ * a fresh capability and re-claim, OR accept the drop. The claim
235
+ * is already inactive on the server side by the time this fires —
236
+ * no client-side action needed unless re-claiming.
237
+ */
238
+ claim_expired: [{
239
+ claimId: string;
240
+ }];
241
+ /**
242
+ * Server rejected an `intent_begin` because another participant
243
+ * already holds an open claim on the same target (cooperative
244
+ * mutex enforced server-side). Surfaces to the participant-level
245
+ * IntentStream so the caller knows their announce was denied.
246
+ * Payload mirrors the wire frame's `payload`.
247
+ */
248
+ intent_rejected: [Record<string, unknown>];
249
+ }
250
+ /**
251
+ * Collaboration event — app-specific real-time events (selection, cursors, etc.)
252
+ * Each event is a [payload] tuple matching the EventEmitter convention.
253
+ */
254
+ export type DefaultCollaborationEvents = Record<string, never>;
255
+ /**
256
+ * Constraint for event maps: every value must be a tuple of handler args.
257
+ *
258
+ * Why a mapped type and not `Record<string, unknown[]>`?
259
+ * `Record<string, ...>` requires an implicit string index signature, which
260
+ * TypeScript interfaces don't have. So a closed interface like Ablo's
261
+ * `AbloCollaborationEvents` would fail to satisfy `Record<string, unknown[]>`,
262
+ * even though every one of its values IS a tuple. This mapped form iterates
263
+ * over `keyof T` instead of demanding a string index, so it accepts both
264
+ * closed interfaces and open Record types — while still enforcing
265
+ * "every value is an array."
266
+ */
267
+ export type EventMap<T> = {
268
+ [K in keyof T]: unknown[];
269
+ };
270
+ /**
271
+ * Full event map = core + collaboration events.
272
+ * Pass your own TCollaboration to add app-specific events.
273
+ */
274
+ export type SyncWebSocketEventMap<TCollaboration extends EventMap<TCollaboration> = DefaultCollaborationEvents> = CoreSyncEventMap & TCollaboration;
275
+ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboration> = DefaultCollaborationEvents> extends EventEmitter {
276
+ /**
277
+ * Subscribe to events with automatic cleanup.
278
+ * Returns unsubscribe function for clean disposal.
279
+ */
280
+ subscribe<K extends keyof SyncWebSocketEventMap<TCollaboration>>(event: K, handler: (...args: SyncWebSocketEventMap<TCollaboration>[K]) => void): () => void;
281
+ /**
282
+ * Send a collaboration event (app-specific real-time message).
283
+ * The wire format is `{ type: messageType, payload: { ...payload, timestamp } }`.
284
+ */
285
+ sendCollaborationEvent<K extends string & keyof TCollaboration>(messageType: K, payload: TCollaboration[K] extends [infer P] ? Omit<P & Record<string, unknown>, 'timestamp'> : never): void;
286
+ private ws;
287
+ private options;
288
+ private reconnectAttempts;
289
+ /** Stop retrying after this many consecutive failures (backoff caps at 30s, so ~7.5 min total) */
290
+ private static readonly MAX_RECONNECT_ATTEMPTS;
291
+ private reconnectTimer;
292
+ /** Periodic catchup interval — polls for missed deltas every 30s while connected */
293
+ private catchupInterval;
294
+ /**
295
+ * Application-level heartbeat. The browser WebSocket API hides RFC 6455
296
+ * protocol-level ping/pong from JavaScript, so the server's `ws.ping()`
297
+ * keepalive can't be observed by client code — meaning the client cannot
298
+ * tell a healthy idle connection apart from a "zombie" socket where TCP
299
+ * silently broke (laptop sleep, NAT timeout, mobile handoff). We send an
300
+ * application-level `{ type: 'ping' }` every 30s and force-close the
301
+ * socket if no inbound traffic arrives within 10s. ANY inbound message
302
+ * counts as proof-of-life — the explicit `pong` is just a guarantee that
303
+ * something will arrive even on an idle stream.
304
+ */
305
+ private heartbeatTimer;
306
+ private heartbeatTimeoutTimer;
307
+ private static readonly HEARTBEAT_INTERVAL_MS;
308
+ private static readonly HEARTBEAT_TIMEOUT_MS;
309
+ private isConnecting;
310
+ private isManualClose;
311
+ /** When true, a session error has been detected (from any path — WS close or HTTP bootstrap).
312
+ * Suppresses reconnection and Sentry error capture to avoid cascading noise. */
313
+ private _sessionErrorDetected;
314
+ /** True once `onopen` has fired at least once on the current socket. Reset each
315
+ * time a new socket is created in `connect()`. Used by `onclose` to detect
316
+ * handshake failures (close before open) — the one signal we have for "the
317
+ * server rejected the upgrade" since browsers hide the HTTP status (e.g.
318
+ * 401) behind the opaque 1006 close code. */
319
+ private _everOpened;
320
+ /**
321
+ * Diagnostic snapshot of the last connection lifecycle. Persisted across
322
+ * the lifetime of the SyncWebSocket so that any subsequent "not connected"
323
+ * rejection can quote the actual root cause (close code + reason + when)
324
+ * instead of bottoming out at a generic error string. Browser WS code 1006
325
+ * hides the real reason, so we layer on our own signals: `forceCloseReason`
326
+ * captures heartbeat trips / send failures, `everOpened` distinguishes
327
+ * handshake reject from mid-session drop, and `sessionErrorAt` tells us
328
+ * whether reconnect is suppressed.
329
+ */
330
+ private lastOpenAt;
331
+ private lastCloseAt;
332
+ private lastCloseCode;
333
+ private lastCloseReason;
334
+ private lastForceCloseReason;
335
+ private sessionErrorAt;
336
+ private lastSyncId;
337
+ private versionVector;
338
+ private syncCursor;
339
+ /** Registered collaboration event keys (colon format) for dispatch in onmessage */
340
+ private collaborationEventTypes;
341
+ /**
342
+ * In-flight `commit` mutation requests keyed by clientTxId. Resolved when
343
+ * a matching `mutation_result` frame arrives from the server, or rejected on
344
+ * timeout / disconnect. Lets consumers await a server ack for mutations
345
+ * sent over the same socket that streams deltas.
346
+ */
347
+ private pendingMutations;
348
+ /**
349
+ * In-flight `claim` requests keyed by claimId. Resolved when the
350
+ * matching `claim_ack` arrives, or rejected on timeout/disconnect.
351
+ * Same shape as pendingMutations — Phoenix-style request/response
352
+ * over a multiplexed connection.
353
+ */
354
+ private pendingClaims;
355
+ constructor(options: SyncWebSocketOptions);
356
+ /**
357
+ * Mark that a session error has been detected (e.g. 401 from HTTP bootstrap).
358
+ * Suppresses further reconnection attempts and Sentry error capture.
359
+ */
360
+ setSessionErrorDetected(): void;
361
+ /**
362
+ * Connect to the sync engine WebSocket
363
+ */
364
+ connect(): void;
365
+ /**
366
+ * Setup WebSocket event handlers
367
+ */
368
+ private setupEventHandlers;
369
+ /**
370
+ * Handle incoming sync delta
371
+ */
372
+ private handleDelta;
373
+ /**
374
+ * Send acknowledgment for received delta with version vector.
375
+ *
376
+ * This is the SOLE forward-mover of `this.lastSyncId` for live
377
+ * deltas. Called by `BaseSyncedStore.flushPendingDeltas` with the
378
+ * `persistedSyncId` watermark — i.e. only after the deltas have
379
+ * actually committed to IDB. Keeping the cursor advance here (rather
380
+ * than at receipt in `handleDelta`/`handleSyncResponse`) means the
381
+ * cursor never gets ahead of the persisted view, so reconnect/
382
+ * catch-up requests can't accidentally skip un-persisted deltas.
383
+ */
384
+ private sendAck;
385
+ /**
386
+ * Public wrapper for sending ack from outside the class
387
+ */
388
+ acknowledge(syncId: number): void;
389
+ /**
390
+ * Send message to server
391
+ */
392
+ send(message: any): void;
393
+ /**
394
+ * Send a `commit` mutation request over the existing WebSocket and
395
+ * resolve when the server's `mutation_result` frame comes back with
396
+ * the same `clientTxId`. The wire-level frame is `{ type: 'commit',
397
+ * payload: { operations, clientTxId } }` — matching the
398
+ * `handleCommit` path on `apps/sync-server/src/hub/Hub.ts` (see the
399
+ * dispatch at Hub.ts:737).
400
+ *
401
+ * Historical naming note: this was originally `sendBatchAck` back when
402
+ * the Go sync-engine used a GraphQL `batchAck` mutation. The TS
403
+ * sync-server uses `type: 'commit'` over WebSocket exclusively. The
404
+ * method name now matches the wire protocol so the ack/commit naming
405
+ * confusion stops here.
406
+ *
407
+ * Times out after 15s of silence from the server. The socket may close
408
+ * during an in-flight mutation (network flap, server restart); we do
409
+ * NOT auto-retry here — the caller's TransactionQueue owns retry +
410
+ * offline replay semantics and the SDK shouldn't duplicate that logic.
411
+ */
412
+ sendCommit(operations: ReadonlyArray<{
413
+ type: string;
414
+ model: string;
415
+ id: string;
416
+ input?: Record<string, unknown>;
417
+ /**
418
+ * Per-op client transaction id. The server stamps this onto
419
+ * `sync_deltas.transaction_id` so the originating client
420
+ * recognizes the broadcast as an echo of its own optimistic
421
+ * mutation (echo detection in `SyncClient.applyDeltaBatchToPool`).
422
+ * Distinct from the batch-level `clientTxId` argument below
423
+ * (which keys `mutation_log` for retry idempotency). See
424
+ * `apps/sync-server/docs/OPTIMISTIC_RECONCILIATION.md`.
425
+ */
426
+ transactionId?: string;
427
+ readAt?: number | null;
428
+ onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
429
+ }>, clientTxId: string, timeoutMs?: number, causedByTaskId?: string | null): Promise<{
430
+ lastSyncId: number;
431
+ }>;
432
+ /**
433
+ * Send a commit frame without waiting for `mutation_result`.
434
+ *
435
+ * This backs the public `wait: 'queued'` API: the socket accepted the
436
+ * frame for delivery, but the server has not confirmed it yet. The
437
+ * eventual `mutation_result` frame is intentionally ignored by this
438
+ * instance because no pending resolver is registered.
439
+ */
440
+ sendCommitQueued(operations: ReadonlyArray<{
441
+ type: string;
442
+ model: string;
443
+ id: string;
444
+ input?: Record<string, unknown>;
445
+ transactionId?: string;
446
+ readAt?: number | null;
447
+ onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
448
+ }>, clientTxId: string, causedByTaskId?: string | null): void;
449
+ /**
450
+ * Activate a participant claim on this connection. Multiplexed
451
+ * subscription pattern (Phoenix Channels / Pusher) — the same
452
+ * connection can hold N concurrent claims, each scoped to a
453
+ * different set of sync groups.
454
+ *
455
+ * Returns a promise that resolves with the server-canonicalized
456
+ * `syncGroups` and effective `ttlSeconds` once `claim_ack` arrives,
457
+ * or rejects with a typed error on `success: false` ack /
458
+ * timeout / disconnect.
459
+ *
460
+ * Why this exists: the old scoped-participant path opened a separate
461
+ * WS per scope. With claims, the SDK reuses the existing session/agent
462
+ * connection — one TCP, N logical participants. See
463
+ * `apps/sync-server/docs/PARTICIPANT_CLAIMS.md` for the migration
464
+ * framing (Phase A.1).
465
+ */
466
+ sendClaim(claimId: string, syncGroups: ReadonlyArray<string>, options?: {
467
+ capabilityToken?: string;
468
+ ttlSeconds?: number;
469
+ timeoutMs?: number;
470
+ }): Promise<{
471
+ syncGroups: string[];
472
+ ttlSeconds?: number;
473
+ }>;
474
+ /**
475
+ * Drop a previously-active claim. Idempotent — `release` is
476
+ * fire-and-forget per the wire contract; the server accepts
477
+ * unknown claimIds silently so disconnect-time release storms
478
+ * never error. No ack is expected.
479
+ *
480
+ * If a claim's send promise is still pending (no claim_ack yet),
481
+ * we reject it locally — the user explicitly chose to release.
482
+ */
483
+ sendRelease(claimId: string): void;
484
+ /**
485
+ * Replace the capability token used for authentication. The new
486
+ * value is read by the next URL-build (i.e., next connect / reconnect
487
+ * cycle). The currently-open WS is NOT torn down — servers keep
488
+ * connections alive past cap expiry until they decide to close, and
489
+ * a forced reconnect would interrupt in-flight deltas. The cap-mint
490
+ * scheduler in `Ablo.ts` calls this on each successful refresh so
491
+ * reconnects after server-initiated close pick up the fresh token.
492
+ */
493
+ setCapabilityToken(token: string): void;
494
+ /**
495
+ * Send spreadsheet selection presence
496
+ */
497
+ sendSheetSelection(sheetId: string, selectedCells: Array<{
498
+ ref: string;
499
+ }>): void;
500
+ /**
501
+ * Send slide layer selection presence
502
+ */
503
+ sendSlideSelection(deckId: string, slideId: string, selectedLayers: Array<{
504
+ layerId: string;
505
+ }>): void;
506
+ /**
507
+ * Send slide cursor position for real-time collaboration
508
+ * Note: Throttling should be handled by the caller (e.g., useSlideCursorBroadcast hook)
509
+ */
510
+ sendSlideCursor(deckId: string, slideId: string, x: number, y: number): void;
511
+ /**
512
+ * Send presence update to server.
513
+ * Use this for:
514
+ * - Updating timezone (improves localTime accuracy shown to other users)
515
+ * - Manual status changes (away, custom status)
516
+ *
517
+ * Note: "online" status is automatically set by server on WebSocket connect,
518
+ * and "offline" is set on disconnect. You don't need to call this for basic online/offline.
519
+ *
520
+ * @param status - "online", "away", or custom status string
521
+ * @param customStatus - Optional custom status message
522
+ */
523
+ sendPresenceUpdate(status?: 'online' | 'away' | 'offline', customStatus?: string): void;
524
+ /**
525
+ * Schedule reconnection with exponential backoff
526
+ */
527
+ private scheduleReconnect;
528
+ /**
529
+ * Reset reconnect attempt counter. Called when network comes back online
530
+ * to allow a fresh reconnect cycle after the max was previously reached.
531
+ */
532
+ resetReconnectAttempts(): void;
533
+ /**
534
+ * Stop the periodic catchup interval
535
+ */
536
+ private stopCatchupInterval;
537
+ /**
538
+ * Disconnect from WebSocket
539
+ */
540
+ disconnect(): void;
541
+ /**
542
+ * Application-level heartbeat. Every `HEARTBEAT_INTERVAL_MS` while
543
+ * `OPEN`, send `{ type: 'ping' }` and arm a `HEARTBEAT_TIMEOUT_MS`
544
+ * watchdog. Any inbound frame (handled in `onmessage`) clears the
545
+ * watchdog. If the watchdog fires, we treat the connection as
546
+ * zombie and force-close it — `onclose` then triggers the existing
547
+ * reconnect path.
548
+ *
549
+ * Why both sides need this:
550
+ * - The server sends RFC 6455 protocol pings via `ws.ping()` every
551
+ * 30s. Browsers auto-respond with a pong but DO NOT expose either
552
+ * frame to JavaScript, so the client is blind to its own keepalive.
553
+ * - On a half-open TCP (laptop wake, NAT timeout, mobile handoff)
554
+ * the browser may keep `readyState === OPEN` for minutes before
555
+ * the OS surfaces the broken connection. App-level traffic is
556
+ * the only signal we can observe.
557
+ */
558
+ private startHeartbeat;
559
+ private stopHeartbeat;
560
+ private clearHeartbeatTimeout;
561
+ /**
562
+ * Force-close the socket from the client side using a private 4xxx
563
+ * code. Callers expect `onclose` to fire; that handler runs the
564
+ * existing reconnect / handshake-failed dispatch. Wrapped in
565
+ * try/catch because `close()` on a CLOSING/CLOSED socket throws on
566
+ * some browsers.
567
+ */
568
+ private forceClose;
569
+ /**
570
+ * Get connection state
571
+ */
572
+ isConnected(): boolean;
573
+ /**
574
+ * Snapshot of recent connection lifecycle state, for diagnostic logs
575
+ * and error messages. Cheap to call (no I/O); safe to log every time
576
+ * a send is rejected so we can attribute "not connected" rejections
577
+ * to the actual root cause (handshake reject vs heartbeat zombie vs
578
+ * session expiry vs explicit close).
579
+ */
580
+ getConnectionDiagnostics(): {
581
+ readyState: number | null;
582
+ isConnecting: boolean;
583
+ isManualClose: boolean;
584
+ sessionErrorDetected: boolean;
585
+ everOpened: boolean;
586
+ reconnectAttempts: number;
587
+ maxReconnectAttempts: number;
588
+ lastOpenAt: number | null;
589
+ lastCloseAt: number | null;
590
+ lastCloseCode: number | null;
591
+ lastCloseReason: string | null;
592
+ lastForceCloseReason: string | null;
593
+ sessionErrorAt: number | null;
594
+ msSinceLastOpen: number | null;
595
+ msSinceLastClose: number | null;
596
+ };
597
+ /**
598
+ * Build a richly-diagnosed "not connected" error so callers (and the
599
+ * logs they emit) can attribute the rejection. The message embeds the
600
+ * dominant signal in human-readable form; the structured detail is
601
+ * also attached as `error.diagnostics` for log scrapers.
602
+ */
603
+ private notConnectedError;
604
+ /** Returns the sync groups this connection is subscribed to. */
605
+ getSyncGroups(): string[];
606
+ /**
607
+ * Update last sync ID (for persistence)
608
+ */
609
+ setLastSyncId(syncId: number): void;
610
+ /**
611
+ * Get current version vector
612
+ */
613
+ getVersionVector(): VersionVector;
614
+ /**
615
+ * Update version vector for specific entity type
616
+ */
617
+ updateVersionVector(entityType: string, version: number): void;
618
+ /**
619
+ * Set version vector (for initialization)
620
+ */
621
+ setVersionVector(versions: VersionVector): void;
622
+ /**
623
+ * Update sync cursor (for incremental sync)
624
+ */
625
+ setSyncCursor(cursor: string | null): void;
626
+ /**
627
+ * Get current sync cursor
628
+ */
629
+ getSyncCursor(): string | null;
630
+ /**
631
+ * Get the highest syncId seen this session (for persistence on clean shutdown)
632
+ */
633
+ getLastSyncId(): number;
634
+ /**
635
+ * Linear-style incremental sync request
636
+ */
637
+ requestIncrementalSync(): Promise<void>;
638
+ /**
639
+ * Request bootstrap for specific entities
640
+ */
641
+ requestBootstrap(entities?: string[]): Promise<void>;
642
+ /**
643
+ * Handle sync response from server
644
+ */
645
+ private handleSyncResponse;
646
+ /**
647
+ * Handle bootstrap response from server
648
+ */
649
+ private handleBootstrapResponse;
650
+ /**
651
+ * Handle presence update from server. The wire frame's payload is
652
+ * forwarded as-is so every consumer (web entity cache,
653
+ * PresenceStream, agent runtime) reads from the same shape.
654
+ * Stripping fields here was a prior bug — it silently dropped
655
+ * `kind`, `activity`, `syncGroups`, `isAgent` for rich consumers.
656
+ *
657
+ * Wire frame (apps/sync-server/src/hub/types.ts PresenceUpdateMessage):
658
+ * { type: 'presence_update', payload: { kind, userId, status,
659
+ * syncGroups, activity, isAgent, timestamp, activeIntents } }
660
+ */
661
+ private handlePresenceUpdate;
662
+ }
663
+ export {};