@abloatai/ablo 0.7.0 → 0.9.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 (181) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.md +80 -66
  3. package/dist/BaseSyncedStore.d.ts +73 -0
  4. package/dist/BaseSyncedStore.js +179 -5
  5. package/dist/Model.d.ts +42 -0
  6. package/dist/Model.js +103 -44
  7. package/dist/SyncEngineContext.d.ts +2 -1
  8. package/dist/SyncEngineContext.js +5 -3
  9. package/dist/agent/session.js +6 -5
  10. package/dist/ai-sdk/coordination-context.js +4 -0
  11. package/dist/ai-sdk/index.d.ts +56 -47
  12. package/dist/ai-sdk/index.js +56 -47
  13. package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
  14. package/dist/ai-sdk/intent-broadcast.js +11 -4
  15. package/dist/ai-sdk/wrap.d.ts +14 -11
  16. package/dist/ai-sdk/wrap.js +11 -13
  17. package/dist/auth/credentialSource.d.ts +34 -0
  18. package/dist/auth/credentialSource.js +63 -0
  19. package/dist/auth/index.d.ts +2 -22
  20. package/dist/auth/index.js +26 -36
  21. package/dist/auth/schemas.d.ts +35 -0
  22. package/dist/auth/schemas.js +53 -0
  23. package/dist/client/Ablo.d.ts +259 -33
  24. package/dist/client/Ablo.js +276 -73
  25. package/dist/client/ApiClient.d.ts +52 -4
  26. package/dist/client/ApiClient.js +236 -66
  27. package/dist/client/auth.d.ts +21 -2
  28. package/dist/client/auth.js +77 -5
  29. package/dist/client/createInternalComponents.d.ts +2 -0
  30. package/dist/client/createInternalComponents.js +8 -1
  31. package/dist/client/createModelProxy.d.ts +187 -79
  32. package/dist/client/createModelProxy.js +203 -68
  33. package/dist/client/httpClient.d.ts +71 -0
  34. package/dist/client/httpClient.js +69 -0
  35. package/dist/client/identity.d.ts +2 -6
  36. package/dist/client/identity.js +63 -11
  37. package/dist/client/index.d.ts +1 -0
  38. package/dist/client/index.js +1 -0
  39. package/dist/client/registerDataSource.d.ts +19 -0
  40. package/dist/client/registerDataSource.js +59 -0
  41. package/dist/client/validateAbloOptions.d.ts +2 -1
  42. package/dist/client/validateAbloOptions.js +8 -7
  43. package/dist/core/DatabaseManager.js +30 -2
  44. package/dist/core/openIDBWithTimeout.d.ts +36 -0
  45. package/dist/core/openIDBWithTimeout.js +88 -1
  46. package/dist/errorCodes.d.ts +92 -1
  47. package/dist/errorCodes.js +139 -7
  48. package/dist/errors.d.ts +54 -3
  49. package/dist/errors.js +192 -44
  50. package/dist/index.d.ts +23 -10
  51. package/dist/index.js +21 -8
  52. package/dist/keys/index.d.ts +76 -0
  53. package/dist/keys/index.js +171 -0
  54. package/dist/mutators/UndoManager.d.ts +86 -50
  55. package/dist/mutators/UndoManager.js +129 -22
  56. package/dist/mutators/inverseOp.d.ts +129 -0
  57. package/dist/mutators/inverseOp.js +74 -0
  58. package/dist/mutators/readerActions.d.ts +1 -1
  59. package/dist/mutators/undoApply.d.ts +42 -0
  60. package/dist/mutators/undoApply.js +143 -0
  61. package/dist/query/client.d.ts +10 -9
  62. package/dist/query/client.js +22 -14
  63. package/dist/react/AbloProvider.d.ts +23 -101
  64. package/dist/react/AbloProvider.js +61 -103
  65. package/dist/react/ClientSideSuspense.d.ts +1 -1
  66. package/dist/react/DefaultFallback.d.ts +1 -1
  67. package/dist/react/SyncGroupProvider.d.ts +1 -1
  68. package/dist/react/index.d.ts +3 -2
  69. package/dist/react/index.js +3 -2
  70. package/dist/react/useAblo.d.ts +4 -4
  71. package/dist/react/useAblo.js +10 -5
  72. package/dist/react/useCurrentUserId.d.ts +1 -1
  73. package/dist/react/useCurrentUserId.js +1 -1
  74. package/dist/react/useMutators.js +19 -12
  75. package/dist/react/useReactive.js +16 -3
  76. package/dist/schema/ddl.d.ts +26 -3
  77. package/dist/schema/ddl.js +152 -4
  78. package/dist/schema/index.d.ts +4 -0
  79. package/dist/schema/index.js +12 -0
  80. package/dist/schema/model.d.ts +11 -0
  81. package/dist/schema/model.js +2 -0
  82. package/dist/schema/openapi.d.ts +28 -0
  83. package/dist/schema/openapi.js +118 -0
  84. package/dist/schema/plane.d.ts +23 -0
  85. package/dist/schema/plane.js +19 -0
  86. package/dist/schema/relation.d.ts +20 -0
  87. package/dist/schema/serialize.d.ts +7 -3
  88. package/dist/schema/serialize.js +6 -2
  89. package/dist/schema/sync-delta-row.d.ts +157 -0
  90. package/dist/schema/sync-delta-row.js +102 -0
  91. package/dist/schema/sync-delta-wire.d.ts +180 -0
  92. package/dist/schema/sync-delta-wire.js +102 -0
  93. package/dist/server/adapter.d.ts +156 -0
  94. package/dist/server/adapter.js +19 -0
  95. package/dist/server/commit.d.ts +82 -0
  96. package/dist/server/commit.js +1 -0
  97. package/dist/server/index.d.ts +14 -0
  98. package/dist/server/index.js +1 -0
  99. package/dist/server/next.d.ts +51 -0
  100. package/dist/server/next.js +47 -0
  101. package/dist/server/read-config.d.ts +60 -0
  102. package/dist/server/read-config.js +8 -0
  103. package/dist/server/storage-mode.d.ts +17 -0
  104. package/dist/server/storage-mode.js +12 -0
  105. package/dist/source/adapter.d.ts +59 -0
  106. package/dist/source/adapter.js +19 -0
  107. package/dist/source/adapters/drizzle.d.ts +34 -0
  108. package/dist/source/adapters/drizzle.js +147 -0
  109. package/dist/source/adapters/memory.d.ts +12 -0
  110. package/dist/source/adapters/memory.js +114 -0
  111. package/dist/source/adapters/prisma.d.ts +57 -0
  112. package/dist/source/adapters/prisma.js +199 -0
  113. package/dist/source/conformance.d.ts +32 -0
  114. package/dist/source/conformance.js +134 -0
  115. package/dist/source/contract.d.ts +143 -0
  116. package/dist/source/contract.js +98 -0
  117. package/dist/source/index.d.ts +61 -10
  118. package/dist/source/index.js +98 -0
  119. package/dist/source/next.d.ts +33 -0
  120. package/dist/source/next.js +26 -0
  121. package/dist/sync/BootstrapHelper.d.ts +10 -0
  122. package/dist/sync/BootstrapHelper.js +56 -42
  123. package/dist/sync/ConnectionManager.d.ts +57 -1
  124. package/dist/sync/ConnectionManager.js +186 -11
  125. package/dist/sync/HydrationCoordinator.d.ts +93 -17
  126. package/dist/sync/HydrationCoordinator.js +241 -41
  127. package/dist/sync/NetworkProbe.d.ts +60 -18
  128. package/dist/sync/NetworkProbe.js +121 -23
  129. package/dist/sync/SyncWebSocket.d.ts +45 -70
  130. package/dist/sync/SyncWebSocket.js +113 -89
  131. package/dist/sync/createIntentStream.js +10 -1
  132. package/dist/sync/participants.js +5 -2
  133. package/dist/transactions/TransactionQueue.js +13 -1
  134. package/dist/types/streams.d.ts +9 -0
  135. package/dist/utils/mobx-setup.js +1 -0
  136. package/dist/webhooks/events.d.ts +38 -0
  137. package/dist/webhooks/events.js +40 -0
  138. package/dist/webhooks/index.d.ts +10 -0
  139. package/dist/webhooks/index.js +10 -0
  140. package/dist/wire/errorEnvelope.d.ts +34 -0
  141. package/dist/wire/errorEnvelope.js +86 -0
  142. package/dist/wire/frames.d.ts +119 -0
  143. package/dist/wire/frames.js +1 -0
  144. package/dist/wire/index.d.ts +24 -0
  145. package/dist/wire/index.js +21 -0
  146. package/dist/wire/listEnvelope.d.ts +45 -0
  147. package/dist/wire/listEnvelope.js +17 -0
  148. package/docs/api-keys.md +5 -5
  149. package/docs/api.md +125 -65
  150. package/docs/audit.md +16 -9
  151. package/docs/cli.md +57 -47
  152. package/docs/client-behavior.md +54 -40
  153. package/docs/coordination.md +66 -80
  154. package/docs/data-sources.md +56 -34
  155. package/docs/examples/agent-human.md +74 -28
  156. package/docs/examples/ai-sdk-tool.md +29 -22
  157. package/docs/examples/existing-python-backend.md +41 -26
  158. package/docs/examples/nextjs.md +32 -17
  159. package/docs/examples/scoped-agent.md +43 -28
  160. package/docs/examples/server-agent.md +40 -15
  161. package/docs/guarantees.md +38 -27
  162. package/docs/identity.md +65 -59
  163. package/docs/index.md +30 -19
  164. package/docs/integration-guide.md +78 -78
  165. package/docs/interaction-model.md +43 -35
  166. package/docs/mcp/claude-code.md +11 -19
  167. package/docs/mcp/cursor.md +7 -25
  168. package/docs/mcp/windsurf.md +7 -20
  169. package/docs/mcp.md +103 -26
  170. package/docs/quickstart.md +63 -61
  171. package/docs/react.md +24 -16
  172. package/docs/roadmap.md +13 -13
  173. package/docs/schema-contract.md +111 -0
  174. package/docs/the-loop.md +21 -0
  175. package/examples/README.md +8 -4
  176. package/examples/data-source/README.md +10 -7
  177. package/examples/data-source/customer-server.ts +27 -25
  178. package/examples/data-source/run.ts +4 -3
  179. package/examples/quickstart.ts +1 -1
  180. package/llms.txt +55 -21
  181. package/package.json +48 -3
@@ -6,12 +6,15 @@
6
6
  * After laptop sleep/wake, it may report true before WiFi/DNS are functional.
7
7
  *
8
8
  * This module provides an authenticated probe against the sync server to verify
9
- * real connectivity + session validity in a single round-trip. The probe hits
10
- * `/api/auth/check`, which runs the SAME auth middleware as the WebSocket
11
- * upgrade path:
12
- * 204 No Content reachable, session cookie valid
13
- * 401/403 → reachable, session expired or invalid
14
- * network failunreachable
9
+ * real connectivity + credential validity in a single round-trip. The probe
10
+ * hits `/api/auth/check`, which runs the SAME auth middleware as the WebSocket
11
+ * upgrade path, and classifies the response into a single {@link ProbeOutcome}
12
+ * via the closed recovery taxonomy ({@link classifyRecovery}):
13
+ * 204 No Content `reachable` (credential valid)
14
+ * 401 `apikey_expired` (ephemeral key)`credential_stale` (re-mint & retry, NO sign-out)
15
+ * 401 `session_expired` / bare 401 → `session_expired` (sign out)
16
+ * 401/403 credential-type/config/perm → `auth_blocked` (stop, no loop, no sign-out)
17
+ * network fail / offline → `unreachable`
15
18
  *
16
19
  * This closes a real gap: the browser's WebSocket API hides HTTP status from
17
20
  * the handshake, so a 401 on the WS upgrade surfaces only as `close code
@@ -21,8 +24,39 @@
21
24
  *
22
25
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
23
26
  */
27
+ import { z } from 'zod';
24
28
  import { getContext } from '../context.js';
25
- import { SyncSessionError } from '../errors.js';
29
+ import { classifyRecovery } from '../errors.js';
30
+ import { withAuthHeaders } from '../auth/credentialSource.js';
31
+ /**
32
+ * The closed set of probe outcomes — one value carrying both reachability and
33
+ * credential disposition, so the {@link ConnectionManager} branches on a single
34
+ * exhaustive discriminant instead of reconstructing intent from a trio of
35
+ * booleans. Mirrors the {@link RecoveryClass} taxonomy at the connectivity tier.
36
+ */
37
+ export const PROBE_OUTCOMES = [
38
+ /** Server reachable and the access credential is currently valid. */
39
+ 'reachable',
40
+ /** Could not reach the server (offline / DNS / TLS / timeout). */
41
+ 'unreachable',
42
+ /** Reachable, but the long-lived login is gone → terminal, sign out. */
43
+ 'session_expired',
44
+ /** Reachable, but the ephemeral access key (`ek_`/`rk_`) expired → silently
45
+ * re-mint a fresh key from the still-valid login and retry. NOT a sign-out. */
46
+ 'credential_stale',
47
+ /** Reachable, but the credential TYPE/config was rejected (wrong key kind,
48
+ * untrusted issuer, no org, a 403) → stop; neither reconnecting nor re-auth
49
+ * helps. Distinct from a sign-out. */
50
+ 'auth_blocked',
51
+ ];
52
+ /** Zod enum derived from {@link PROBE_OUTCOMES}. */
53
+ export const probeOutcomeSchema = z.enum(PROBE_OUTCOMES);
54
+ /** Result of a network probe: a single {@link ProbeOutcome} plus round-trip
55
+ * latency (null when the probe never completed). */
56
+ export const probeResultSchema = z.object({
57
+ outcome: probeOutcomeSchema,
58
+ latencyMs: z.number().nullable(),
59
+ });
26
60
  const PROBE_TIMEOUT_MS = 4000;
27
61
  /**
28
62
  * Derive the probe URL from a sync-server base URL. Accepts `ws://`,
@@ -46,11 +80,14 @@ function resolveProbeUrl(baseUrl) {
46
80
  * Returns reachability AND session status in a single call, so the
47
81
  * ConnectionStore can make the right state transition without guessing.
48
82
  *
49
- * @param baseUrl The sync-server base URL (HTTP or WS scheme accepted).
50
- * If omitted, falls back to `NEXT_PUBLIC_GO_SERVER_URL`
51
- * `http://localhost:8080` for backwards compatibility.
83
+ * @param input The sync-server base URL (HTTP or WS scheme accepted), or an
84
+ * options bag with `authToken`. A bare string is still accepted
85
+ * for backwards compatibility.
52
86
  */
53
- export async function probeNetwork(baseUrl) {
87
+ export async function probeNetwork(input) {
88
+ const baseUrl = typeof input === 'string' ? input : input?.baseUrl;
89
+ const getAuthToken = typeof input === 'string' ? undefined : input?.getAuthToken;
90
+ const authToken = typeof input === 'string' ? undefined : input?.authToken;
54
91
  const url = resolveProbeUrl(baseUrl);
55
92
  // Fast-fail: if navigator.onLine is false, skip the probe entirely.
56
93
  // This is the ONE case where navigator.onLine is reliable (MDN: "false
@@ -58,29 +95,90 @@ export async function probeNetwork(baseUrl) {
58
95
  // because Node 22+ exposes `navigator` with `onLine === undefined`,
59
96
  // and `!undefined === true` would short-circuit the probe server-side.
60
97
  if (typeof navigator !== 'undefined' && navigator.onLine === false) {
61
- return { reachable: false, sessionValid: null, latencyMs: null };
98
+ return { outcome: 'unreachable', latencyMs: null };
62
99
  }
63
100
  const controller = new AbortController();
64
101
  const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
65
102
  const start = performance.now();
66
103
  try {
104
+ const headers = withAuthHeaders(getAuthToken, { 'Cache-Control': 'no-cache' }, authToken);
67
105
  const response = await fetch(url, {
68
106
  method: 'HEAD',
69
- credentials: 'include', // Send cookies for session check
70
107
  signal: controller.signal,
71
108
  // Cache-bust to avoid stale responses
72
- headers: { 'Cache-Control': 'no-cache' },
109
+ headers,
73
110
  });
74
111
  const latencyMs = Math.round(performance.now() - start);
75
- if (SyncSessionError.isSessionErrorResponse(response.status)) {
76
- // Server reachable but session expired/invalid
77
- getContext().logger.info('[NetworkProbe] Server reachable, session expired', {
78
- status: response.status,
112
+ // The probe is a HEAD (no body), but the sync-server sets `X-Auth-Failure:
113
+ // <code>` on every auth rejection. Route the code through the closed
114
+ // recovery taxonomy so each failure mode gets its correct outcome — the
115
+ // whole reason this taxonomy exists: an expired ephemeral key
116
+ // (`access_credential_expiry`) must re-mint, NOT sign the user out the way
117
+ // a genuine login expiry (`session_expiry`) does, and NOT wedge the way a
118
+ // credential-type/config rejection (`auth_blocked`) does.
119
+ const authFailure = response.headers.get('x-auth-failure');
120
+ if (authFailure) {
121
+ const recovery = classifyRecovery(authFailure);
122
+ switch (recovery) {
123
+ case 'session_expiry':
124
+ getContext().logger.info('[NetworkProbe] Server reachable, login expired', {
125
+ status: response.status,
126
+ code: authFailure,
127
+ latencyMs,
128
+ });
129
+ return { outcome: 'session_expired', latencyMs };
130
+ case 'access_credential_expiry':
131
+ getContext().logger.info('[NetworkProbe] Server reachable, access key stale — will re-mint', {
132
+ status: response.status,
133
+ code: authFailure,
134
+ latencyMs,
135
+ });
136
+ return { outcome: 'credential_stale', latencyMs };
137
+ case 'auth_blocked':
138
+ case 'permission':
139
+ case 'none':
140
+ // A non-expiry auth rejection — wrong credential type/config, a 403,
141
+ // or an auth-tagged code this SDK doesn't recognise. Re-auth re-mints
142
+ // the same rejected credential and retrying won't help, so STOP
143
+ // rather than reconnect-loop or sign the user out.
144
+ getContext().logger.warn('[NetworkProbe] Reachable but auth-blocked (non-retryable, non-expiry)', {
145
+ status: response.status,
146
+ code: authFailure,
147
+ recovery,
148
+ latencyMs,
149
+ });
150
+ return { outcome: 'auth_blocked', latencyMs };
151
+ case 'transient':
152
+ // Retryable auth-tagged response — connectivity is proven; fall
153
+ // through to `reachable` and let the normal retry path handle it.
154
+ break;
155
+ default: {
156
+ const _exhaustive = recovery;
157
+ void _exhaustive;
158
+ }
159
+ }
160
+ }
161
+ else if (response.status === 401) {
162
+ // Bare 401 with no READABLE structured code. This is AMBIGUOUS and must
163
+ // NOT sign the user out on its own — two common causes are both
164
+ // recoverable, and only one is a real logout:
165
+ // 1. The server DID send `X-Auth-Failure: apikey_expired`, but it's a
166
+ // custom header on a cross-origin response and the server didn't list
167
+ // it in `Access-Control-Expose-Headers`, so the browser stripped it to
168
+ // null (the network-change logout bug). The access key just needs a
169
+ // re-mint.
170
+ // 2. A genuinely expired access key on a non-Ablo proxy / cookie path.
171
+ // So route to `credential_stale`: the FSM attempts a re-mint, and the ONLY
172
+ // way to actually sign out is the re-mint resolving `null` (login truly
173
+ // gone). If no refresher is wired, the bounded attempt counter falls
174
+ // through to `auth_blocked` (stop) — still never a spurious logout. This
175
+ // upholds the invariant: null is the only terminal path, never a bare 401.
176
+ getContext().logger.info('[NetworkProbe] Server reachable, bare 401 — re-mint (not sign-out)', {
79
177
  latencyMs,
80
178
  });
81
- return { reachable: true, sessionValid: false, latencyMs };
179
+ return { outcome: 'credential_stale', latencyMs };
82
180
  }
83
- // 2xx (including 204) means reachable + session valid.
181
+ // 2xx (including 204) means reachable + credential valid.
84
182
  // 3xx/4xx (non-auth) still prove connectivity even though the probe
85
183
  // expected 204; log a warning so misconfigurations surface instead of
86
184
  // silently passing.
@@ -92,12 +190,12 @@ export async function probeNetwork(baseUrl) {
92
190
  });
93
191
  }
94
192
  else {
95
- getContext().logger.debug('[NetworkProbe] Server reachable, session valid', {
193
+ getContext().logger.debug('[NetworkProbe] Server reachable, credential valid', {
96
194
  status: response.status,
97
195
  latencyMs,
98
196
  });
99
197
  }
100
- return { reachable: true, sessionValid: true, latencyMs };
198
+ return { outcome: 'reachable', latencyMs };
101
199
  }
102
200
  catch (error) {
103
201
  clearTimeout(timeout);
@@ -105,7 +203,7 @@ export async function probeNetwork(baseUrl) {
105
203
  getContext().logger.info('[NetworkProbe] Probe failed', {
106
204
  reason: isAbort ? 'timeout' : error.message,
107
205
  });
108
- return { reachable: false, sessionValid: null, latencyMs: null };
206
+ return { outcome: 'unreachable', latencyMs: null };
109
207
  }
110
208
  finally {
111
209
  clearTimeout(timeout);
@@ -8,40 +8,18 @@
8
8
  * - Automatic reconnection with exponential backoff
9
9
  */
10
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
- }
11
+ import type { MutationOperation } from '../interfaces/index.js';
12
+ import type { ClientSyncDelta } from '../schema/sync-delta-wire.js';
13
+ import { type AuthTokenGetter } from '../auth/credentialSource.js';
14
+ /**
15
+ * The wire delta the client receives. Derived from the canonical
16
+ * `clientSyncDeltaSchema` (`@abloatai/ablo/schema`) via `z.infer` so the
17
+ * SDK and the sync-server share ONE contract instead of two hand-maintained
18
+ * interfaces. The action vocabulary (`I`/`U`/`D`/`A`/`V`/`C`/`G`/`S`) and the
19
+ * client-only extras (`metadata`, `clientMutationId`, deprecated flat
20
+ * `createdBy`) live in that schema; see its doc for the full field reference.
21
+ */
22
+ export type SyncDelta = ClientSyncDelta;
45
23
  /**
46
24
  * Payload for legacy actionType 'G' deltas emitted by EmitGroupChange.
47
25
  * Carries both added and removed groups in one delta, forces full re-bootstrap.
@@ -121,6 +99,15 @@ export interface SyncWebSocketOptions {
121
99
  * the Biscuit→opaque-key migration.)
122
100
  */
123
101
  capabilityToken?: string;
102
+ /**
103
+ * Shared credential getter. When provided, WebSocket URL auth reads this
104
+ * instead of a copied `capabilityToken`, so reconnects use refreshed tokens
105
+ * from the SDK's single auth source.
106
+ */
107
+ /** Shared SDK auth getter. Preferred internal name. */
108
+ getAuthToken?: AuthTokenGetter;
109
+ /** @deprecated Use `getAuthToken`. Kept for direct low-level callers. */
110
+ getCapabilityToken?: AuthTokenGetter;
124
111
  }
125
112
  /**
126
113
  * Bootstrap hint from server indicating full or partial bootstrap is needed.
@@ -271,7 +258,7 @@ export interface CoreSyncEventMap {
271
258
  /**
272
259
  * Per-entity wait-queue snapshot: `{ target, queue: Intent[] }` with each
273
260
  * entry `status: 'queued'` + `position`. Broadcast to entity peers on every
274
- * queue mutation — powers the reactive `ablo.<model>.queue(id)` read.
261
+ * queue mutation — powers the reactive `ablo.<model>.claim.queue({ id })` read.
275
262
  */
276
263
  intent_queue: [Record<string, unknown>];
277
264
  intent_acquired: [Record<string, unknown>];
@@ -422,6 +409,16 @@ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboratio
422
409
  * Send message to server
423
410
  */
424
411
  send(message: any): void;
412
+ /**
413
+ * Project the SDK's `MutationOperation[]` onto the canonical wire
414
+ * `CommitMessage`. This is the single serialize boundary between the SDK op
415
+ * type (loose `type: string`, plus an SDK-internal `options` the server never
416
+ * reads) and the strict wire contract. The per-field map gives compile-time
417
+ * drift detection (a `CommitOperation` shape change breaks here) and the lone
418
+ * `as` narrows the validated op `type` to the wire union — the only
419
+ * loosening, localized to this boundary.
420
+ */
421
+ private buildCommitFrame;
425
422
  /**
426
423
  * Send a `commit` mutation request over the existing WebSocket and
427
424
  * resolve when the server's `mutation_result` frame comes back with
@@ -441,24 +438,7 @@ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboratio
441
438
  * NOT auto-retry here — the caller's TransactionQueue owns retry +
442
439
  * offline replay semantics and the SDK shouldn't duplicate that logic.
443
440
  */
444
- sendCommit(operations: ReadonlyArray<{
445
- type: string;
446
- model: string;
447
- id: string;
448
- input?: Record<string, unknown>;
449
- /**
450
- * Per-op client transaction id. The server stamps this onto
451
- * `sync_deltas.transaction_id` so the originating client
452
- * recognizes the broadcast as an echo of its own optimistic
453
- * mutation (echo detection in `SyncClient.applyDeltaBatchToPool`).
454
- * Distinct from the batch-level `clientTxId` argument below
455
- * (which keys `mutation_log` for retry idempotency). See
456
- * `apps/sync-server/docs/OPTIMISTIC_RECONCILIATION.md`.
457
- */
458
- transactionId?: string;
459
- readAt?: number | null;
460
- onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
461
- }>, clientTxId: string, timeoutMs?: number, causedByTaskId?: string | null): Promise<{
441
+ sendCommit(operations: ReadonlyArray<MutationOperation>, clientTxId: string, timeoutMs?: number, causedByTaskId?: string | null): Promise<{
462
442
  lastSyncId: number;
463
443
  }>;
464
444
  /**
@@ -469,15 +449,7 @@ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboratio
469
449
  * eventual `mutation_result` frame is intentionally ignored by this
470
450
  * instance because no pending resolver is registered.
471
451
  */
472
- sendCommitQueued(operations: ReadonlyArray<{
473
- type: string;
474
- model: string;
475
- id: string;
476
- input?: Record<string, unknown>;
477
- transactionId?: string;
478
- readAt?: number | null;
479
- onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
480
- }>, clientTxId: string, causedByTaskId?: string | null): void;
452
+ sendCommitQueued(operations: ReadonlyArray<MutationOperation>, clientTxId: string, causedByTaskId?: string | null): void;
481
453
  /**
482
454
  * Activate a participant claim on this connection. Multiplexed
483
455
  * subscription pattern (Phoenix Channels / Pusher) — the same
@@ -514,15 +486,19 @@ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboratio
514
486
  */
515
487
  sendRelease(claimId: string): void;
516
488
  /**
517
- * Replace the capability token used for authentication. The new
518
- * value is read by the next URL-build (i.e., next connect / reconnect
519
- * cycle). The currently-open WS is NOT torn down — servers keep
520
- * connections alive past cap expiry until they decide to close, and
521
- * a forced reconnect would interrupt in-flight deltas. The cap-mint
522
- * scheduler in `Ablo.ts` calls this on each successful refresh so
523
- * reconnects after server-initiated close pick up the fresh token.
489
+ * Compatibility setter for direct SyncWebSocket users. The SDK-owned
490
+ * `Ablo()` path passes `getAuthToken`, so reconnect URL auth reads the
491
+ * shared credential source instead of this copied value.
524
492
  */
525
493
  setCapabilityToken(token: string): void;
494
+ getAuthToken(): string | undefined;
495
+ /**
496
+ * Return the credential that will be used by the next WebSocket upgrade.
497
+ * ConnectionManager reads this for HTTP auth probes so visibility/network
498
+ * checks authenticate the same way reconnects do.
499
+ */
500
+ getCapabilityToken(): string | undefined;
501
+ private resolveAuthToken;
526
502
  /**
527
503
  * Send spreadsheet selection presence
528
504
  */
@@ -692,4 +668,3 @@ export declare class SyncWebSocket<TCollaboration extends EventMap<TCollaboratio
692
668
  */
693
669
  private handlePresenceUpdate;
694
670
  }
695
- export {};