@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.
- package/CHANGELOG.md +72 -1
- package/README.md +80 -66
- package/dist/BaseSyncedStore.d.ts +73 -0
- package/dist/BaseSyncedStore.js +179 -5
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/SyncEngineContext.d.ts +2 -1
- package/dist/SyncEngineContext.js +5 -3
- package/dist/agent/session.js +6 -5
- package/dist/ai-sdk/coordination-context.js +4 -0
- package/dist/ai-sdk/index.d.ts +56 -47
- package/dist/ai-sdk/index.js +56 -47
- package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
- package/dist/ai-sdk/intent-broadcast.js +11 -4
- package/dist/ai-sdk/wrap.d.ts +14 -11
- package/dist/ai-sdk/wrap.js +11 -13
- package/dist/auth/credentialSource.d.ts +34 -0
- package/dist/auth/credentialSource.js +63 -0
- package/dist/auth/index.d.ts +2 -22
- package/dist/auth/index.js +26 -36
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +259 -33
- package/dist/client/Ablo.js +276 -73
- package/dist/client/ApiClient.d.ts +52 -4
- package/dist/client/ApiClient.js +236 -66
- package/dist/client/auth.d.ts +21 -2
- package/dist/client/auth.js +77 -5
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +187 -79
- package/dist/client/createModelProxy.js +203 -68
- package/dist/client/httpClient.d.ts +71 -0
- package/dist/client/httpClient.js +69 -0
- package/dist/client/identity.d.ts +2 -6
- package/dist/client/identity.js +63 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +19 -0
- package/dist/client/registerDataSource.js +59 -0
- package/dist/client/validateAbloOptions.d.ts +2 -1
- package/dist/client/validateAbloOptions.js +8 -7
- package/dist/core/DatabaseManager.js +30 -2
- package/dist/core/openIDBWithTimeout.d.ts +36 -0
- package/dist/core/openIDBWithTimeout.js +88 -1
- package/dist/errorCodes.d.ts +92 -1
- package/dist/errorCodes.js +139 -7
- package/dist/errors.d.ts +54 -3
- package/dist/errors.js +192 -44
- package/dist/index.d.ts +23 -10
- package/dist/index.js +21 -8
- package/dist/keys/index.d.ts +76 -0
- package/dist/keys/index.js +171 -0
- package/dist/mutators/UndoManager.d.ts +86 -50
- package/dist/mutators/UndoManager.js +129 -22
- package/dist/mutators/inverseOp.d.ts +129 -0
- package/dist/mutators/inverseOp.js +74 -0
- package/dist/mutators/readerActions.d.ts +1 -1
- package/dist/mutators/undoApply.d.ts +42 -0
- package/dist/mutators/undoApply.js +143 -0
- package/dist/query/client.d.ts +10 -9
- package/dist/query/client.js +22 -14
- package/dist/react/AbloProvider.d.ts +23 -101
- package/dist/react/AbloProvider.js +61 -103
- package/dist/react/ClientSideSuspense.d.ts +1 -1
- package/dist/react/DefaultFallback.d.ts +1 -1
- package/dist/react/SyncGroupProvider.d.ts +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +3 -2
- package/dist/react/useAblo.d.ts +4 -4
- package/dist/react/useAblo.js +10 -5
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- package/dist/react/useReactive.js +16 -3
- package/dist/schema/ddl.d.ts +26 -3
- package/dist/schema/ddl.js +152 -4
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.js +12 -0
- package/dist/schema/model.d.ts +11 -0
- package/dist/schema/model.js +2 -0
- package/dist/schema/openapi.d.ts +28 -0
- package/dist/schema/openapi.js +118 -0
- package/dist/schema/plane.d.ts +23 -0
- package/dist/schema/plane.js +19 -0
- package/dist/schema/relation.d.ts +20 -0
- package/dist/schema/serialize.d.ts +7 -3
- package/dist/schema/serialize.js +6 -2
- package/dist/schema/sync-delta-row.d.ts +157 -0
- package/dist/schema/sync-delta-row.js +102 -0
- package/dist/schema/sync-delta-wire.d.ts +180 -0
- package/dist/schema/sync-delta-wire.js +102 -0
- package/dist/server/adapter.d.ts +156 -0
- package/dist/server/adapter.js +19 -0
- package/dist/server/commit.d.ts +82 -0
- package/dist/server/commit.js +1 -0
- package/dist/server/index.d.ts +14 -0
- package/dist/server/index.js +1 -0
- package/dist/server/next.d.ts +51 -0
- package/dist/server/next.js +47 -0
- package/dist/server/read-config.d.ts +60 -0
- package/dist/server/read-config.js +8 -0
- package/dist/server/storage-mode.d.ts +17 -0
- package/dist/server/storage-mode.js +12 -0
- package/dist/source/adapter.d.ts +59 -0
- package/dist/source/adapter.js +19 -0
- package/dist/source/adapters/drizzle.d.ts +34 -0
- package/dist/source/adapters/drizzle.js +147 -0
- package/dist/source/adapters/memory.d.ts +12 -0
- package/dist/source/adapters/memory.js +114 -0
- package/dist/source/adapters/prisma.d.ts +57 -0
- package/dist/source/adapters/prisma.js +199 -0
- package/dist/source/conformance.d.ts +32 -0
- package/dist/source/conformance.js +134 -0
- package/dist/source/contract.d.ts +143 -0
- package/dist/source/contract.js +98 -0
- package/dist/source/index.d.ts +61 -10
- package/dist/source/index.js +98 -0
- package/dist/source/next.d.ts +33 -0
- package/dist/source/next.js +26 -0
- package/dist/sync/BootstrapHelper.d.ts +10 -0
- package/dist/sync/BootstrapHelper.js +56 -42
- package/dist/sync/ConnectionManager.d.ts +57 -1
- package/dist/sync/ConnectionManager.js +186 -11
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +241 -41
- package/dist/sync/NetworkProbe.d.ts +60 -18
- package/dist/sync/NetworkProbe.js +121 -23
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +113 -89
- package/dist/sync/createIntentStream.js +10 -1
- package/dist/sync/participants.js +5 -2
- package/dist/transactions/TransactionQueue.js +13 -1
- package/dist/types/streams.d.ts +9 -0
- package/dist/utils/mobx-setup.js +1 -0
- package/dist/webhooks/events.d.ts +38 -0
- package/dist/webhooks/events.js +40 -0
- package/dist/webhooks/index.d.ts +10 -0
- package/dist/webhooks/index.js +10 -0
- package/dist/wire/errorEnvelope.d.ts +34 -0
- package/dist/wire/errorEnvelope.js +86 -0
- package/dist/wire/frames.d.ts +119 -0
- package/dist/wire/frames.js +1 -0
- package/dist/wire/index.d.ts +24 -0
- package/dist/wire/index.js +21 -0
- package/dist/wire/listEnvelope.d.ts +45 -0
- package/dist/wire/listEnvelope.js +17 -0
- package/docs/api-keys.md +5 -5
- package/docs/api.md +125 -65
- package/docs/audit.md +16 -9
- package/docs/cli.md +57 -47
- package/docs/client-behavior.md +54 -40
- package/docs/coordination.md +66 -80
- package/docs/data-sources.md +56 -34
- package/docs/examples/agent-human.md +74 -28
- package/docs/examples/ai-sdk-tool.md +29 -22
- package/docs/examples/existing-python-backend.md +41 -26
- package/docs/examples/nextjs.md +32 -17
- package/docs/examples/scoped-agent.md +43 -28
- package/docs/examples/server-agent.md +40 -15
- package/docs/guarantees.md +38 -27
- package/docs/identity.md +65 -59
- package/docs/index.md +30 -19
- package/docs/integration-guide.md +78 -78
- package/docs/interaction-model.md +43 -35
- package/docs/mcp/claude-code.md +11 -19
- package/docs/mcp/cursor.md +7 -25
- package/docs/mcp/windsurf.md +7 -20
- package/docs/mcp.md +103 -26
- package/docs/quickstart.md +63 -61
- package/docs/react.md +24 -16
- package/docs/roadmap.md +13 -13
- package/docs/schema-contract.md +111 -0
- package/docs/the-loop.md +21 -0
- package/examples/README.md +8 -4
- package/examples/data-source/README.md +10 -7
- package/examples/data-source/customer-server.ts +27 -25
- package/examples/data-source/run.ts +4 -3
- package/examples/quickstart.ts +1 -1
- package/llms.txt +55 -21
- 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 +
|
|
10
|
-
* `/api/auth/check`, which runs the SAME auth middleware as the WebSocket
|
|
11
|
-
* upgrade path
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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 {
|
|
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
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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(
|
|
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 {
|
|
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
|
|
109
|
+
headers,
|
|
73
110
|
});
|
|
74
111
|
const latencyMs = Math.round(performance.now() - start);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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 {
|
|
179
|
+
return { outcome: 'credential_stale', latencyMs };
|
|
82
180
|
}
|
|
83
|
-
// 2xx (including 204) means reachable +
|
|
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,
|
|
193
|
+
getContext().logger.debug('[NetworkProbe] Server reachable, credential valid', {
|
|
96
194
|
status: response.status,
|
|
97
195
|
latencyMs,
|
|
98
196
|
});
|
|
99
197
|
}
|
|
100
|
-
return {
|
|
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 {
|
|
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
|
-
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
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 {};
|