@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.
- package/CHANGELOG.md +208 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +230 -0
- package/dist/BaseSyncedStore.d.ts +709 -0
- package/dist/BaseSyncedStore.js +1843 -0
- package/dist/Database.d.ts +344 -0
- package/dist/Database.js +1259 -0
- package/dist/LazyReferenceCollection.d.ts +181 -0
- package/dist/LazyReferenceCollection.js +460 -0
- package/dist/Model.d.ts +339 -0
- package/dist/Model.js +715 -0
- package/dist/ModelRegistry.d.ts +200 -0
- package/dist/ModelRegistry.js +535 -0
- package/dist/NetworkMonitor.d.ts +27 -0
- package/dist/NetworkMonitor.js +73 -0
- package/dist/ObjectPool.d.ts +202 -0
- package/dist/ObjectPool.js +1106 -0
- package/dist/SyncClient.d.ts +489 -0
- package/dist/SyncClient.js +1555 -0
- package/dist/SyncEngineContext.d.ts +46 -0
- package/dist/SyncEngineContext.js +74 -0
- package/dist/adapters/alwaysOnline.d.ts +16 -0
- package/dist/adapters/alwaysOnline.js +19 -0
- package/dist/adapters/inMemoryStorage.d.ts +30 -0
- package/dist/adapters/inMemoryStorage.js +94 -0
- package/dist/agent/Agent.d.ts +358 -0
- package/dist/agent/Agent.js +500 -0
- package/dist/agent/index.d.ts +115 -0
- package/dist/agent/index.js +128 -0
- package/dist/agent/session.d.ts +90 -0
- package/dist/agent/session.js +156 -0
- package/dist/agent/types.d.ts +73 -0
- package/dist/agent/types.js +10 -0
- package/dist/ai-sdk/coordination-context.d.ts +51 -0
- package/dist/ai-sdk/coordination-context.js +107 -0
- package/dist/ai-sdk/index.d.ts +68 -0
- package/dist/ai-sdk/index.js +68 -0
- package/dist/ai-sdk/intent-broadcast.d.ts +77 -0
- package/dist/ai-sdk/intent-broadcast.js +72 -0
- package/dist/ai-sdk/wrap.d.ts +67 -0
- package/dist/ai-sdk/wrap.js +45 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +9 -0
- package/dist/auth/index.d.ts +137 -0
- package/dist/auth/index.js +246 -0
- package/dist/client/Ablo.d.ts +835 -0
- package/dist/client/Ablo.js +1440 -0
- package/dist/client/ApiClient.d.ts +200 -0
- package/dist/client/ApiClient.js +659 -0
- package/dist/client/auth.d.ts +79 -0
- package/dist/client/auth.js +81 -0
- package/dist/client/createInternalComponents.d.ts +44 -0
- package/dist/client/createInternalComponents.js +88 -0
- package/dist/client/createModelProxy.d.ts +152 -0
- package/dist/client/createModelProxy.js +199 -0
- package/dist/client/identity.d.ts +63 -0
- package/dist/client/identity.js +156 -0
- package/dist/client/index.d.ts +36 -0
- package/dist/client/index.js +33 -0
- package/dist/client/persistence.d.ts +7 -0
- package/dist/client/persistence.js +11 -0
- package/dist/client/validateAbloOptions.d.ts +42 -0
- package/dist/client/validateAbloOptions.js +43 -0
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.js +12 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +58 -0
- package/dist/core/DatabaseManager.d.ts +108 -0
- package/dist/core/DatabaseManager.js +361 -0
- package/dist/core/QueryProcessor.d.ts +77 -0
- package/dist/core/QueryProcessor.js +262 -0
- package/dist/core/QueryView.d.ts +64 -0
- package/dist/core/QueryView.js +219 -0
- package/dist/core/StoreManager.d.ts +131 -0
- package/dist/core/StoreManager.js +334 -0
- package/dist/core/ViewRegistry.d.ts +20 -0
- package/dist/core/ViewRegistry.js +55 -0
- package/dist/core/index.d.ts +34 -0
- package/dist/core/index.js +59 -0
- package/dist/core/openIDBWithTimeout.d.ts +27 -0
- package/dist/core/openIDBWithTimeout.js +63 -0
- package/dist/core/query-utils.d.ts +37 -0
- package/dist/core/query-utils.js +60 -0
- package/dist/errors.d.ts +235 -0
- package/dist/errors.js +243 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +82 -0
- package/dist/interfaces/headless.d.ts +95 -0
- package/dist/interfaces/headless.js +41 -0
- package/dist/interfaces/index.d.ts +321 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/mutators/RecordingTransaction.d.ts +36 -0
- package/dist/mutators/RecordingTransaction.js +216 -0
- package/dist/mutators/Transaction.d.ts +48 -0
- package/dist/mutators/Transaction.js +64 -0
- package/dist/mutators/UndoManager.d.ts +114 -0
- package/dist/mutators/UndoManager.js +143 -0
- package/dist/mutators/defineMutators.d.ts +55 -0
- package/dist/mutators/defineMutators.js +28 -0
- package/dist/policy/index.d.ts +19 -0
- package/dist/policy/index.js +18 -0
- package/dist/policy/types.d.ts +74 -0
- package/dist/policy/types.js +17 -0
- package/dist/principal.d.ts +44 -0
- package/dist/principal.js +49 -0
- package/dist/query/client.d.ts +43 -0
- package/dist/query/client.js +84 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +5 -0
- package/dist/query/types.d.ts +143 -0
- package/dist/query/types.js +36 -0
- package/dist/react/AbloProvider.d.ts +205 -0
- package/dist/react/AbloProvider.js +398 -0
- package/dist/react/ClientSideSuspense.d.ts +36 -0
- package/dist/react/ClientSideSuspense.js +17 -0
- package/dist/react/DefaultFallback.d.ts +24 -0
- package/dist/react/DefaultFallback.js +43 -0
- package/dist/react/SyncGroupProvider.d.ts +19 -0
- package/dist/react/SyncGroupProvider.js +44 -0
- package/dist/react/context.d.ts +161 -0
- package/dist/react/context.js +35 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +73 -0
- package/dist/react/internalContext.d.ts +35 -0
- package/dist/react/internalContext.js +3 -0
- package/dist/react/useAblo.d.ts +72 -0
- package/dist/react/useAblo.js +63 -0
- package/dist/react/useCurrentUserId.d.ts +21 -0
- package/dist/react/useCurrentUserId.js +33 -0
- package/dist/react/useErrorListener.d.ts +20 -0
- package/dist/react/useErrorListener.js +39 -0
- package/dist/react/useIntent.d.ts +29 -0
- package/dist/react/useIntent.js +42 -0
- package/dist/react/useMutate.d.ts +83 -0
- package/dist/react/useMutate.js +122 -0
- package/dist/react/useMutationFailureListener.d.ts +26 -0
- package/dist/react/useMutationFailureListener.js +38 -0
- package/dist/react/useMutators.d.ts +56 -0
- package/dist/react/useMutators.js +66 -0
- package/dist/react/usePresence.d.ts +32 -0
- package/dist/react/usePresence.js +41 -0
- package/dist/react/useQuery.d.ts +123 -0
- package/dist/react/useQuery.js +145 -0
- package/dist/react/useReactive.d.ts +35 -0
- package/dist/react/useReactive.js +111 -0
- package/dist/react/useReader.d.ts +69 -0
- package/dist/react/useReader.js +73 -0
- package/dist/react/useSyncStatus.d.ts +61 -0
- package/dist/react/useSyncStatus.js +76 -0
- package/dist/react/useUndoScope.d.ts +36 -0
- package/dist/react/useUndoScope.js +73 -0
- package/dist/realtime/index.d.ts +10 -0
- package/dist/realtime/index.js +9 -0
- package/dist/schema/field.d.ts +134 -0
- package/dist/schema/field.js +264 -0
- package/dist/schema/index.d.ts +29 -0
- package/dist/schema/index.js +38 -0
- package/dist/schema/model.d.ts +326 -0
- package/dist/schema/model.js +89 -0
- package/dist/schema/queries.d.ts +203 -0
- package/dist/schema/queries.js +145 -0
- package/dist/schema/relation.d.ts +172 -0
- package/dist/schema/relation.js +104 -0
- package/dist/schema/schema.d.ts +259 -0
- package/dist/schema/schema.js +188 -0
- package/dist/schema/sugar.d.ts +129 -0
- package/dist/schema/sugar.js +94 -0
- package/dist/source/index.d.ts +423 -0
- package/dist/source/index.js +320 -0
- package/dist/source/pushQueue.d.ts +112 -0
- package/dist/source/pushQueue.js +249 -0
- package/dist/stores/ObjectStore.d.ts +103 -0
- package/dist/stores/ObjectStore.js +371 -0
- package/dist/stores/ObjectStoreContract.d.ts +39 -0
- package/dist/stores/ObjectStoreContract.js +1 -0
- package/dist/stores/SyncActionStore.d.ts +101 -0
- package/dist/stores/SyncActionStore.js +481 -0
- package/dist/sync/BootstrapHelper.d.ts +127 -0
- package/dist/sync/BootstrapHelper.js +434 -0
- package/dist/sync/ConnectionManager.d.ts +136 -0
- package/dist/sync/ConnectionManager.js +465 -0
- package/dist/sync/HydrationCoordinator.d.ts +137 -0
- package/dist/sync/HydrationCoordinator.js +468 -0
- package/dist/sync/NetworkProbe.d.ts +43 -0
- package/dist/sync/NetworkProbe.js +113 -0
- package/dist/sync/OfflineFlush.d.ts +9 -0
- package/dist/sync/OfflineFlush.js +22 -0
- package/dist/sync/OfflineTransactionStore.d.ts +37 -0
- package/dist/sync/OfflineTransactionStore.js +263 -0
- package/dist/sync/SyncWebSocket.d.ts +663 -0
- package/dist/sync/SyncWebSocket.js +1336 -0
- package/dist/sync/createIntentStream.d.ts +33 -0
- package/dist/sync/createIntentStream.js +243 -0
- package/dist/sync/createPresenceStream.d.ts +46 -0
- package/dist/sync/createPresenceStream.js +192 -0
- package/dist/sync/createSnapshot.d.ts +33 -0
- package/dist/sync/createSnapshot.js +124 -0
- package/dist/sync/participants.d.ts +114 -0
- package/dist/sync/participants.js +336 -0
- package/dist/sync/schemas.d.ts +79 -0
- package/dist/sync/schemas.js +78 -0
- package/dist/testing/fixtures/bootstrap.d.ts +45 -0
- package/dist/testing/fixtures/bootstrap.js +53 -0
- package/dist/testing/fixtures/deltas.d.ts +86 -0
- package/dist/testing/fixtures/deltas.js +139 -0
- package/dist/testing/fixtures/models.d.ts +82 -0
- package/dist/testing/fixtures/models.js +270 -0
- package/dist/testing/helpers/react-wrapper.d.ts +66 -0
- package/dist/testing/helpers/react-wrapper.js +64 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +55 -0
- package/dist/testing/helpers/sync-engine-harness.js +70 -0
- package/dist/testing/helpers/wait.d.ts +25 -0
- package/dist/testing/helpers/wait.js +44 -0
- package/dist/testing/index.d.ts +21 -0
- package/dist/testing/index.js +32 -0
- package/dist/testing/mocks/MockMutationExecutor.d.ts +65 -0
- package/dist/testing/mocks/MockMutationExecutor.js +139 -0
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +20 -0
- package/dist/testing/mocks/MockNetworkMonitor.js +46 -0
- package/dist/testing/mocks/MockSyncContext.d.ts +64 -0
- package/dist/testing/mocks/MockSyncContext.js +100 -0
- package/dist/testing/mocks/MockSyncStore.d.ts +88 -0
- package/dist/testing/mocks/MockSyncStore.js +171 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +66 -0
- package/dist/testing/mocks/MockWebSocket.js +117 -0
- package/dist/transactions/OptimisticEchoTracker.d.ts +82 -0
- package/dist/transactions/OptimisticEchoTracker.js +104 -0
- package/dist/transactions/TransactionQueue.d.ts +499 -0
- package/dist/transactions/TransactionQueue.js +1895 -0
- package/dist/transactions/index.d.ts +16 -0
- package/dist/transactions/index.js +7 -0
- package/dist/transactions/mutation-error-handler.d.ts +5 -0
- package/dist/transactions/mutation-error-handler.js +39 -0
- package/dist/types/global.d.ts +107 -0
- package/dist/types/global.js +38 -0
- package/dist/types/index.d.ts +241 -0
- package/dist/types/index.js +70 -0
- package/dist/types/streams.d.ts +495 -0
- package/dist/types/streams.js +11 -0
- package/dist/utils/asyncIterator.d.ts +41 -0
- package/dist/utils/asyncIterator.js +142 -0
- package/dist/utils/duration.d.ts +28 -0
- package/dist/utils/duration.js +47 -0
- package/dist/utils/mobx-setup.d.ts +42 -0
- package/dist/utils/mobx-setup.js +381 -0
- package/docs/api-keys.md +24 -0
- package/docs/api.md +230 -0
- package/docs/audit.md +81 -0
- package/docs/capabilities.md +163 -0
- package/docs/client-behavior.md +202 -0
- package/docs/data-sources.md +214 -0
- package/docs/examples/agent-human.md +84 -0
- package/docs/examples/ai-sdk-tool.md +92 -0
- package/docs/examples/existing-python-backend.md +249 -0
- package/docs/examples/nextjs.md +88 -0
- package/docs/examples/server-agent.md +86 -0
- package/docs/guarantees.md +148 -0
- package/docs/index.md +97 -0
- package/docs/integration-guide.md +493 -0
- package/docs/interaction-model.md +140 -0
- package/docs/mcp/claude-code.md +43 -0
- package/docs/mcp/cursor.md +53 -0
- package/docs/mcp/windsurf.md +46 -0
- package/docs/mcp.md +59 -0
- package/docs/quickstart.md +152 -0
- package/docs/react.md +115 -0
- package/docs/roadmap.md +45 -0
- package/examples/README.md +54 -0
- package/examples/data-source/README.md +102 -0
- package/examples/data-source/ablo-driver.ts +89 -0
- package/examples/data-source/customer-server.ts +208 -0
- package/examples/data-source/run.ts +101 -0
- package/examples/data-source/schema.ts +25 -0
- package/examples/quickstart.ts +54 -0
- package/examples/tsconfig.json +16 -0
- package/llms.txt +143 -0
- 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 {};
|