@abloatai/ablo 0.8.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 +40 -1
- package/README.md +32 -27
- package/dist/BaseSyncedStore.d.ts +73 -0
- package/dist/BaseSyncedStore.js +172 -2
- package/dist/Model.d.ts +42 -0
- package/dist/Model.js +103 -44
- package/dist/agent/session.js +3 -3
- 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 +4 -42
- package/dist/auth/schemas.d.ts +35 -0
- package/dist/auth/schemas.js +53 -0
- package/dist/client/Ablo.d.ts +160 -42
- package/dist/client/Ablo.js +145 -75
- package/dist/client/ApiClient.d.ts +20 -4
- package/dist/client/ApiClient.js +166 -28
- package/dist/client/auth.d.ts +14 -5
- package/dist/client/auth.js +60 -7
- package/dist/client/createInternalComponents.d.ts +2 -0
- package/dist/client/createInternalComponents.js +8 -1
- package/dist/client/createModelProxy.d.ts +130 -66
- package/dist/client/createModelProxy.js +152 -49
- 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 +49 -11
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/registerDataSource.d.ts +3 -3
- package/dist/client/registerDataSource.js +11 -9
- package/dist/client/validateAbloOptions.js +1 -1
- 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 +70 -1
- package/dist/errorCodes.js +108 -9
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +72 -22
- package/dist/index.d.ts +17 -8
- package/dist/index.js +15 -6
- package/dist/keys/index.d.ts +16 -1
- package/dist/keys/index.js +26 -6
- 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 +3 -6
- package/dist/react/AbloProvider.d.ts +23 -126
- package/dist/react/AbloProvider.js +62 -199
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useCurrentUserId.d.ts +1 -1
- package/dist/react/useCurrentUserId.js +1 -1
- package/dist/react/useMutators.js +19 -12
- 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 +4 -0
- package/dist/schema/serialize.js +4 -0
- 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 +10 -15
- package/dist/sync/ConnectionManager.d.ts +55 -1
- package/dist/sync/ConnectionManager.js +155 -16
- package/dist/sync/HydrationCoordinator.d.ts +93 -17
- package/dist/sync/HydrationCoordinator.js +238 -39
- package/dist/sync/NetworkProbe.d.ts +58 -24
- package/dist/sync/NetworkProbe.js +118 -42
- package/dist/sync/SyncWebSocket.d.ts +45 -70
- package/dist/sync/SyncWebSocket.js +70 -36
- package/dist/sync/createIntentStream.js +10 -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.md +47 -44
- package/docs/cli.md +44 -44
- package/docs/client-behavior.md +30 -30
- package/docs/coordination.md +33 -36
- package/docs/data-sources.md +35 -15
- package/docs/examples/agent-human.md +45 -43
- package/docs/examples/ai-sdk-tool.md +20 -16
- package/docs/examples/existing-python-backend.md +16 -12
- package/docs/examples/nextjs.md +14 -12
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/examples/server-agent.md +24 -21
- package/docs/guarantees.md +15 -13
- package/docs/index.md +1 -1
- package/docs/integration-guide.md +30 -30
- package/docs/interaction-model.md +19 -23
- package/docs/mcp/claude-code.md +3 -3
- package/docs/mcp/cursor.md +1 -1
- package/docs/mcp/windsurf.md +2 -2
- package/docs/mcp.md +6 -6
- package/docs/quickstart.md +41 -31
- package/docs/react.md +13 -9
- package/docs/schema-contract.md +12 -10
- package/docs/the-loop.md +21 -0
- package/examples/data-source/README.md +4 -5
- package/examples/data-source/customer-server.ts +27 -25
- package/llms.txt +28 -5
- package/package.json +43 -3
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single mutable source for the SDK's active bearer credential.
|
|
3
|
+
*
|
|
4
|
+
* Every transport should read from this object at request/connect time:
|
|
5
|
+
* bootstrap HTTP, lazy query HTTP, identity/probe HTTP, and WebSocket URL
|
|
6
|
+
* auth. Token refresh writes here once; consumers observe the new value
|
|
7
|
+
* through their getter without being manually patched one by one.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* WebSocket subprotocols used to carry the bearer credential OUT of the URL.
|
|
11
|
+
*
|
|
12
|
+
* Browsers cannot set an `Authorization` header on a WebSocket, so the SDK
|
|
13
|
+
* offers the token as a `Sec-WebSocket-Protocol` value — `ablo.bearer.<token>` —
|
|
14
|
+
* alongside the real `ablo.sync.v1` protocol the server selects. This keeps the
|
|
15
|
+
* credential out of the query string, which ALB access logs, proxies, and
|
|
16
|
+
* browser history capture. The server reads the token from the subprotocol and
|
|
17
|
+
* echoes back ONLY `ablo.sync.v1`, never the token-bearing value. Shared with
|
|
18
|
+
* the sync-server so client and server can never drift on the wire format.
|
|
19
|
+
*/
|
|
20
|
+
export const WS_BEARER_SUBPROTOCOL_PREFIX = 'ablo.bearer.';
|
|
21
|
+
export const WS_SYNC_SUBPROTOCOL = 'ablo.sync.v1';
|
|
22
|
+
export function createAuthCredentialSource(initialToken) {
|
|
23
|
+
let authToken = normalizeToken(initialToken);
|
|
24
|
+
return {
|
|
25
|
+
getAuthToken: () => authToken,
|
|
26
|
+
setAuthToken(token) {
|
|
27
|
+
authToken = normalizeToken(token);
|
|
28
|
+
},
|
|
29
|
+
authorizationHeader() {
|
|
30
|
+
return authorizationHeaderForToken(authToken);
|
|
31
|
+
},
|
|
32
|
+
withAuthHeaders(headers = {}) {
|
|
33
|
+
const authorization = authorizationHeaderForToken(authToken);
|
|
34
|
+
return authorization ? { ...headers, Authorization: authorization } : { ...headers };
|
|
35
|
+
},
|
|
36
|
+
applyAuthQueryParam(params, paramName = 'authorization') {
|
|
37
|
+
applyAuthToQueryParams(params, () => authToken, paramName);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function resolveAuthToken(getAuthToken, fallbackToken) {
|
|
42
|
+
return normalizeToken(getAuthToken?.() ?? fallbackToken) ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
export function authorizationHeaderForToken(token) {
|
|
45
|
+
const normalized = normalizeToken(token);
|
|
46
|
+
return normalized ? `Bearer ${normalized}` : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function withAuthHeaders(getAuthToken, headers = {}, fallbackToken) {
|
|
49
|
+
const authorization = authorizationHeaderForToken(resolveAuthToken(getAuthToken, fallbackToken));
|
|
50
|
+
return authorization ? { ...headers, Authorization: authorization } : { ...headers };
|
|
51
|
+
}
|
|
52
|
+
export function applyAuthToQueryParams(params, getAuthToken, paramName = 'authorization', fallbackToken) {
|
|
53
|
+
const authorization = authorizationHeaderForToken(resolveAuthToken(getAuthToken, fallbackToken));
|
|
54
|
+
if (authorization) {
|
|
55
|
+
params.set(paramName, authorization);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function normalizeToken(token) {
|
|
59
|
+
if (!token)
|
|
60
|
+
return null;
|
|
61
|
+
const trimmed = token.trim();
|
|
62
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
63
|
+
}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -11,21 +11,8 @@
|
|
|
11
11
|
* SDKs hide their internal auth-handshake — the apiKey is the only
|
|
12
12
|
* credential the consumer touches.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
readonly capabilityId: string;
|
|
17
|
-
readonly token: string;
|
|
18
|
-
readonly expiresAt: string;
|
|
19
|
-
readonly organizationId: string;
|
|
20
|
-
readonly scope: {
|
|
21
|
-
readonly organizationId: string;
|
|
22
|
-
readonly syncGroups: readonly string[];
|
|
23
|
-
readonly operations: readonly string[];
|
|
24
|
-
readonly participantKind: 'user' | 'agent' | 'system';
|
|
25
|
-
readonly participantId: string;
|
|
26
|
-
};
|
|
27
|
-
readonly userMeta: Record<string, unknown>;
|
|
28
|
-
}
|
|
14
|
+
import { type CapabilityExchangeResponse, type IdentityResolveResponse } from './schemas.js';
|
|
15
|
+
export type { CapabilityExchangeResponse, IdentityResolveResponse } from './schemas.js';
|
|
29
16
|
export interface ExchangeApiKeyRequest {
|
|
30
17
|
readonly apiKey: string;
|
|
31
18
|
readonly baseUrl: string;
|
|
@@ -41,13 +28,6 @@ export interface ExchangeApiKeyRequest {
|
|
|
41
28
|
readonly timeoutMs?: number;
|
|
42
29
|
}
|
|
43
30
|
export declare function exchangeApiKey(options: ExchangeApiKeyRequest): Promise<CapabilityExchangeResponse>;
|
|
44
|
-
export interface IdentityResolveResponse {
|
|
45
|
-
readonly participantKind: 'user' | 'agent' | 'system';
|
|
46
|
-
readonly participantId: string;
|
|
47
|
-
readonly accountScope: string;
|
|
48
|
-
readonly syncGroups: readonly string[];
|
|
49
|
-
readonly userMeta: Record<string, unknown>;
|
|
50
|
-
}
|
|
51
31
|
export interface ResolveIdentityRequest {
|
|
52
32
|
readonly baseUrl: string;
|
|
53
33
|
readonly authToken?: string;
|
package/dist/auth/index.js
CHANGED
|
@@ -11,25 +11,8 @@
|
|
|
11
11
|
* SDKs hide their internal auth-handshake — the apiKey is the only
|
|
12
12
|
* credential the consumer touches.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
* Whether an HTTP error body carries a code `translateHttpError` can read —
|
|
17
|
-
* a top-level `code`, a nested `error.code`, or a string `error`. When it
|
|
18
|
-
* doesn't (empty body, non-JSON, or a non-Ablo proxy 401), the caller falls
|
|
19
|
-
* back to its own default code rather than emitting a code-less error.
|
|
20
|
-
*/
|
|
21
|
-
function hasWireCode(body) {
|
|
22
|
-
if (typeof body !== 'object' || body === null)
|
|
23
|
-
return false;
|
|
24
|
-
const b = body;
|
|
25
|
-
if (typeof b.code === 'string')
|
|
26
|
-
return true;
|
|
27
|
-
if (typeof b.error === 'string')
|
|
28
|
-
return true;
|
|
29
|
-
return (typeof b.error === 'object' &&
|
|
30
|
-
b.error !== null &&
|
|
31
|
-
typeof b.error.code === 'string');
|
|
32
|
-
}
|
|
14
|
+
import { parseCapabilityExchangeResponse, parseIdentityResolveResponse, } from './schemas.js';
|
|
15
|
+
import { AbloAuthenticationError, hasWireCode, translateHttpError } from '../errors.js';
|
|
33
16
|
export async function exchangeApiKey(options) {
|
|
34
17
|
if (!options.apiKey) {
|
|
35
18
|
throw new AbloAuthenticationError('apiKey is required for capability exchange', { code: 'apikey_missing' });
|
|
@@ -88,27 +71,7 @@ export async function exchangeApiKey(options) {
|
|
|
88
71
|
? translateHttpError(response.status, body, requestId)
|
|
89
72
|
: new AbloAuthenticationError(`apiKey exchange rejected (${response.status})`, { code: 'exchange_failed', httpStatus: response.status });
|
|
90
73
|
}
|
|
91
|
-
|
|
92
|
-
if (!isCapabilityExchangeResponse(raw)) {
|
|
93
|
-
throw new AbloAuthenticationError('apiKey exchange response was malformed — missing required fields', { code: 'exchange_malformed_response' });
|
|
94
|
-
}
|
|
95
|
-
return raw;
|
|
96
|
-
}
|
|
97
|
-
function isCapabilityExchangeResponse(raw) {
|
|
98
|
-
if (!raw || typeof raw !== 'object')
|
|
99
|
-
return false;
|
|
100
|
-
const o = raw;
|
|
101
|
-
if (typeof o.token !== 'string')
|
|
102
|
-
return false;
|
|
103
|
-
if (typeof o.expiresAt !== 'string')
|
|
104
|
-
return false;
|
|
105
|
-
if (typeof o.organizationId !== 'string')
|
|
106
|
-
return false;
|
|
107
|
-
if (typeof o.scope !== 'object' || o.scope === null)
|
|
108
|
-
return false;
|
|
109
|
-
if (typeof o.userMeta !== 'object' || o.userMeta === null)
|
|
110
|
-
return false;
|
|
111
|
-
return true;
|
|
74
|
+
return parseCapabilityExchangeResponse(await response.json());
|
|
112
75
|
}
|
|
113
76
|
/**
|
|
114
77
|
* Resolve the caller's Ablo identity from the authenticated request
|
|
@@ -135,7 +98,6 @@ export async function resolveIdentity(options) {
|
|
|
135
98
|
response = await fetcher(url, {
|
|
136
99
|
method: 'GET',
|
|
137
100
|
headers,
|
|
138
|
-
credentials: 'include',
|
|
139
101
|
signal: controller.signal,
|
|
140
102
|
});
|
|
141
103
|
}
|
|
@@ -164,7 +126,7 @@ export async function resolveIdentity(options) {
|
|
|
164
126
|
? translateHttpError(response.status, body, requestId)
|
|
165
127
|
: new AbloAuthenticationError(`identity resolve rejected (${response.status})`, { code: 'identity_resolve_failed', httpStatus: response.status });
|
|
166
128
|
}
|
|
167
|
-
return (await response.json());
|
|
129
|
+
return parseIdentityResolveResponse(await response.json());
|
|
168
130
|
}
|
|
169
131
|
const DEFAULT_BUFFER_FLOOR_MS = 60_000;
|
|
170
132
|
const DEFAULT_BUFFER_RATIO = 0.1;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AuthTokenSchema: z.ZodString;
|
|
3
|
+
export declare const CapabilityExchangeResponseSchema: z.ZodObject<{
|
|
4
|
+
capabilityId: z.ZodString;
|
|
5
|
+
token: z.ZodString;
|
|
6
|
+
expiresAt: z.ZodString;
|
|
7
|
+
organizationId: z.ZodString;
|
|
8
|
+
scope: z.ZodObject<{
|
|
9
|
+
organizationId: z.ZodString;
|
|
10
|
+
syncGroups: z.ZodArray<z.ZodString>;
|
|
11
|
+
operations: z.ZodArray<z.ZodString>;
|
|
12
|
+
participantKind: z.ZodEnum<{
|
|
13
|
+
user: "user";
|
|
14
|
+
agent: "agent";
|
|
15
|
+
system: "system";
|
|
16
|
+
}>;
|
|
17
|
+
participantId: z.ZodString;
|
|
18
|
+
}, z.core.$loose>;
|
|
19
|
+
userMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
20
|
+
}, z.core.$loose>;
|
|
21
|
+
export type CapabilityExchangeResponse = z.infer<typeof CapabilityExchangeResponseSchema>;
|
|
22
|
+
export declare const IdentityResolveResponseSchema: z.ZodObject<{
|
|
23
|
+
participantKind: z.ZodEnum<{
|
|
24
|
+
user: "user";
|
|
25
|
+
agent: "agent";
|
|
26
|
+
system: "system";
|
|
27
|
+
}>;
|
|
28
|
+
participantId: z.ZodString;
|
|
29
|
+
accountScope: z.ZodString;
|
|
30
|
+
syncGroups: z.ZodArray<z.ZodString>;
|
|
31
|
+
userMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
32
|
+
}, z.core.$loose>;
|
|
33
|
+
export type IdentityResolveResponse = z.infer<typeof IdentityResolveResponseSchema>;
|
|
34
|
+
export declare function parseCapabilityExchangeResponse(raw: unknown): CapabilityExchangeResponse;
|
|
35
|
+
export declare function parseIdentityResolveResponse(raw: unknown): IdentityResolveResponse;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AbloAuthenticationError } from '../errors.js';
|
|
3
|
+
const AuthParticipantKindSchema = z.enum(['user', 'agent', 'system']);
|
|
4
|
+
export const AuthTokenSchema = z.string().trim().min(1);
|
|
5
|
+
export const CapabilityExchangeResponseSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
capabilityId: z.string().min(1),
|
|
8
|
+
token: AuthTokenSchema,
|
|
9
|
+
expiresAt: z.string().min(1),
|
|
10
|
+
organizationId: z.string().min(1),
|
|
11
|
+
scope: z
|
|
12
|
+
.object({
|
|
13
|
+
organizationId: z.string().min(1),
|
|
14
|
+
syncGroups: z.array(z.string()),
|
|
15
|
+
operations: z.array(z.string()),
|
|
16
|
+
participantKind: AuthParticipantKindSchema,
|
|
17
|
+
participantId: z.string().min(1),
|
|
18
|
+
})
|
|
19
|
+
.passthrough(),
|
|
20
|
+
userMeta: z.record(z.string(), z.unknown()),
|
|
21
|
+
})
|
|
22
|
+
.passthrough();
|
|
23
|
+
export const IdentityResolveResponseSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
participantKind: AuthParticipantKindSchema,
|
|
26
|
+
participantId: z.string().min(1),
|
|
27
|
+
accountScope: z.string().min(1),
|
|
28
|
+
syncGroups: z.array(z.string()),
|
|
29
|
+
userMeta: z.record(z.string(), z.unknown()),
|
|
30
|
+
})
|
|
31
|
+
.passthrough();
|
|
32
|
+
function formatIssues(error) {
|
|
33
|
+
return error.issues
|
|
34
|
+
.map((issue) => {
|
|
35
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '<root>';
|
|
36
|
+
return `${path}: ${issue.message}`;
|
|
37
|
+
})
|
|
38
|
+
.join('; ');
|
|
39
|
+
}
|
|
40
|
+
export function parseCapabilityExchangeResponse(raw) {
|
|
41
|
+
const parsed = CapabilityExchangeResponseSchema.safeParse(raw);
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
throw new AbloAuthenticationError(`apiKey exchange response was malformed: ${formatIssues(parsed.error)}`, { code: 'exchange_malformed_response', cause: parsed.error });
|
|
44
|
+
}
|
|
45
|
+
return parsed.data;
|
|
46
|
+
}
|
|
47
|
+
export function parseIdentityResolveResponse(raw) {
|
|
48
|
+
const parsed = IdentityResolveResponseSchema.safeParse(raw);
|
|
49
|
+
if (!parsed.success) {
|
|
50
|
+
throw new AbloAuthenticationError(`identity resolve response was malformed: ${formatIssues(parsed.error)}`, { code: 'identity_resolve_failed', cause: parsed.error });
|
|
51
|
+
}
|
|
52
|
+
return parsed.data;
|
|
53
|
+
}
|
package/dist/client/Ablo.d.ts
CHANGED
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
* const sync = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
|
|
12
12
|
*
|
|
13
13
|
* const reports = sync.reports.list({ where: { status: 'todo' } });
|
|
14
|
-
* await sync.reports.create({ title: 'Fix bug' });
|
|
15
|
-
* await sync.reports.update(
|
|
16
|
-
*
|
|
14
|
+
* await sync.reports.create({ data: { title: 'Fix bug' } });
|
|
15
|
+
* await sync.reports.update({
|
|
16
|
+
* id: reportId,
|
|
17
|
+
* data: { status: 'ready' },
|
|
18
|
+
* });
|
|
19
|
+
* await sync.reports.delete({ id: reportId });
|
|
17
20
|
*/
|
|
18
21
|
import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
|
|
19
22
|
import type { SyncEngineConfig, SyncLogger, MutationExecutor, MutationDispatcher, SyncObservabilityProvider, SyncAnalytics, SessionErrorDetector, OnlineStatusProvider } from '../interfaces/index.js';
|
|
@@ -23,7 +26,7 @@ import type { SyncWebSocket } from '../sync/SyncWebSocket.js';
|
|
|
23
26
|
import { type SyncStatus } from '../BaseSyncedStore.js';
|
|
24
27
|
import type { IntentStream, IntentWaitOptions, PresenceStream, Snapshot } from '../types/streams.js';
|
|
25
28
|
import type { ParticipantManager } from '../sync/participants.js';
|
|
26
|
-
import type { ActiveIntent, Duration, TargetRange } from '../types/streams.js';
|
|
29
|
+
import type { ActiveIntent, Duration, Intent, TargetRange } from '../types/streams.js';
|
|
27
30
|
import { type AbloApi, type AbloApiClientOptions, type AbloApiIntents } from './ApiClient.js';
|
|
28
31
|
/**
|
|
29
32
|
* Handle returned by `engine.beginTurn()`. While alive, every commit
|
|
@@ -71,7 +74,7 @@ import { type AbloPersistence } from './persistence.js';
|
|
|
71
74
|
* the way you'd reach for the equivalent option on the Stripe / OpenAI
|
|
72
75
|
* / Anthropic clients: rarely, and deliberately.
|
|
73
76
|
*
|
|
74
|
-
* @see https://docs.
|
|
77
|
+
* @see https://docs.abloatai.com — full option reference
|
|
75
78
|
*/
|
|
76
79
|
export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
77
80
|
/**
|
|
@@ -81,26 +84,53 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
81
84
|
*/
|
|
82
85
|
schema: Schema<S>;
|
|
83
86
|
/**
|
|
84
|
-
* API key
|
|
87
|
+
* API key — **the one auth field most apps set.** Server-side this is your
|
|
88
|
+
* secret `sk_` (and it defaults to `process.env['ABLO_API_KEY']`, so you
|
|
89
|
+
* usually pass nothing). A long-lived key needs no refresh; the client uses
|
|
90
|
+
* it as-is.
|
|
85
91
|
*
|
|
86
|
-
* Accepts a static string
|
|
87
|
-
*
|
|
88
|
-
*
|
|
92
|
+
* Accepts a static string or an async `() => Promise<string>` resolver if you
|
|
93
|
+
* rotate keys out-of-band (resolved at bootstrap).
|
|
94
|
+
*
|
|
95
|
+
* Browser apps that mint a SHORT-LIVED per-user key (`ek_`) from a login can't
|
|
96
|
+
* ship a secret — those use {@link getToken} (or {@link authEndpoint}) instead,
|
|
97
|
+
* which the client refreshes for you. That's the only case that isn't "just
|
|
98
|
+
* `apiKey`".
|
|
89
99
|
*/
|
|
90
100
|
apiKey?: string | ApiKeySetter | null | undefined;
|
|
91
101
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
102
|
+
* Opt-in for the SHORT-LIVED per-user browser case: an async resolver for a
|
|
103
|
+
* fresh bearer (`ek_`/`rk_`) your backend minted for the signed-in user. The
|
|
104
|
+
* client calls it once before connect and then keeps the key fresh for you —
|
|
105
|
+
* a refresh timer ahead of expiry plus re-mint on OS-wake / network-online /
|
|
106
|
+
* tab-focus, and a reactive re-mint when a probe finds the key stale. You
|
|
107
|
+
* never call a refresh method (Supabase `autoRefreshToken` model).
|
|
108
|
+
*
|
|
109
|
+
* Contract: resolve a token, resolve `null` when the login itself is gone
|
|
110
|
+
* (terminal → sign out), or THROW on a transient failure (→ back off, never
|
|
111
|
+
* sign out). Leave unset for the static-`apiKey` path.
|
|
112
|
+
*/
|
|
113
|
+
getToken?: (() => Promise<string | null>) | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Convenience over {@link getToken}: a URL on YOUR backend that returns
|
|
116
|
+
* `{ token }`. The client POSTs to it (with cookies, so it's authed by the
|
|
117
|
+
* user's session) to mint + refresh the bearer. Ignored when `getToken` is
|
|
118
|
+
* set. Pure sugar — `getToken: () => fetch(url).then(r => r.json()).then(b => b.token)`.
|
|
119
|
+
*/
|
|
120
|
+
authEndpoint?: string | undefined;
|
|
121
|
+
/**
|
|
122
|
+
* Direct-URL convenience connector: a connection string to your own Postgres
|
|
123
|
+
* that Ablo can register for a dedicated tenant.
|
|
124
|
+
*
|
|
125
|
+
* This is NOT the default Data Source path. For the Zero-shaped default, keep
|
|
126
|
+
* `DATABASE_URL` in your app, expose `dataSource(...)`, and let your server
|
|
127
|
+
* write the database while Ablo coordinates the sync stream.
|
|
96
128
|
*
|
|
97
129
|
* SERVER-ONLY: this carries credentials, so it is never sent from the browser
|
|
98
130
|
* — constructing a client with `databaseUrl` and `dangerouslyAllowBrowser`
|
|
99
|
-
* throws.
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* Omit it to use Ablo-managed storage (the hosted default).
|
|
131
|
+
* throws. If you opt into this connector, provide a NON-superuser,
|
|
132
|
+
* non-`BYPASSRLS` role; the direct connector rejects privileged roles that
|
|
133
|
+
* cannot enforce RLS.
|
|
104
134
|
*/
|
|
105
135
|
databaseUrl?: string | null | undefined;
|
|
106
136
|
/**
|
|
@@ -159,8 +189,8 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
159
189
|
authToken?: string | null | undefined;
|
|
160
190
|
/**
|
|
161
191
|
* Override the default base URL. Defaults to
|
|
162
|
-
* `wss://
|
|
163
|
-
* URL for self-hosted or
|
|
192
|
+
* `wss://api.abloatai.com` for hosted production; pass an explicit
|
|
193
|
+
* URL for self-hosted or private deployments.
|
|
164
194
|
*/
|
|
165
195
|
baseURL?: string | null | undefined;
|
|
166
196
|
/**
|
|
@@ -194,6 +224,18 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
194
224
|
* only for the advanced Model / Claim / Commit client.
|
|
195
225
|
*/
|
|
196
226
|
schema: Schema<S>;
|
|
227
|
+
/**
|
|
228
|
+
* Short-lived-bearer resolver for the per-user browser path (mirrors the
|
|
229
|
+
* public {@link AbloOptions.getToken}). The client mints the first token
|
|
230
|
+
* before connect and refreshes it (timer + wake/online/focus) — see
|
|
231
|
+
* {@link resolveCredentialResolver}.
|
|
232
|
+
*/
|
|
233
|
+
getToken?: (() => Promise<string | null>) | undefined;
|
|
234
|
+
/**
|
|
235
|
+
* Backend URL returning `{ token }`; sugar over {@link getToken}. Mirrors the
|
|
236
|
+
* public {@link AbloOptions.authEndpoint}.
|
|
237
|
+
*/
|
|
238
|
+
authEndpoint?: string | undefined;
|
|
197
239
|
/**
|
|
198
240
|
* @deprecated Server derives participant kind from the apiKey's
|
|
199
241
|
* scope. Pass apiKey only; this option will be removed once the
|
|
@@ -289,8 +331,8 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
289
331
|
* `commit` method against `${url}/graphql`) with one that uses your own
|
|
290
332
|
* GraphQL client, auth headers, retry policy, and observability hooks.
|
|
291
333
|
*
|
|
292
|
-
* Default: a fetch-based executor that targets `${url}/graphql`
|
|
293
|
-
*
|
|
334
|
+
* Default: a fetch-based executor that targets `${url}/graphql` and sends
|
|
335
|
+
* the configured bearer (`apiKey` / backend-minted token) as `Authorization`.
|
|
294
336
|
*/
|
|
295
337
|
mutationExecutor?: MutationExecutor;
|
|
296
338
|
/**
|
|
@@ -336,18 +378,14 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
336
378
|
* Operations available on each model in the sync engine.
|
|
337
379
|
*
|
|
338
380
|
* Naming aligns with Stripe / OpenAI / Anthropic conventions:
|
|
339
|
-
* `retrieve(id)` — single
|
|
340
|
-
* `list({where})` — collection
|
|
341
|
-
* `
|
|
342
|
-
* `
|
|
343
|
-
* `
|
|
344
|
-
*
|
|
345
|
-
* The old verb set (`findById`, `findMany`, `findFirst`) is kept as
|
|
346
|
-
* deprecated aliases for one release cycle so consumers can migrate
|
|
347
|
-
* without a flag day.
|
|
381
|
+
* `retrieve({ id })` — async single-row server read
|
|
382
|
+
* `list({ where })` — async collection server read
|
|
383
|
+
* `get(id)` / `getAll(...)` / `getCount(...)` — local graph snapshots
|
|
384
|
+
* `create({ data })` / `update({ id, data })` / `delete({ id })` — writes
|
|
385
|
+
* `claim({ id })` — durable claim handle for coordinated writes
|
|
348
386
|
*/
|
|
349
|
-
export type { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ClaimOptions,
|
|
350
|
-
import type { ModelOperations } from './createModelProxy.js';
|
|
387
|
+
export type { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './createModelProxy.js';
|
|
388
|
+
import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelLoadOptions } from './createModelProxy.js';
|
|
351
389
|
export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
|
|
352
390
|
export type CommitWait = 'queued' | 'confirmed';
|
|
353
391
|
export interface ModelTarget {
|
|
@@ -364,6 +402,7 @@ export interface ModelClaim {
|
|
|
364
402
|
readonly actor: string;
|
|
365
403
|
readonly participantKind: ActiveIntent['participantKind'];
|
|
366
404
|
readonly action: string;
|
|
405
|
+
readonly description?: string;
|
|
367
406
|
readonly field?: string;
|
|
368
407
|
readonly status?: 'active' | 'queued';
|
|
369
408
|
readonly position?: number;
|
|
@@ -465,14 +504,64 @@ export interface ModelMutationOptions extends ClaimedOptions {
|
|
|
465
504
|
readonly readAt?: number | null;
|
|
466
505
|
readonly onStale?: 'reject' | 'force' | 'flag' | 'merge' | null;
|
|
467
506
|
readonly wait?: CommitWait;
|
|
507
|
+
readonly claim?: ClaimHandle | ClaimOptions | null;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The HTTP/stateless claim surface. Normal tools usually put `claim` directly
|
|
511
|
+
* on the write (`update({ id, data, claim })`) and let the SDK release it. Use
|
|
512
|
+
* this namespace for multi-step handles and coordination screens.
|
|
513
|
+
*/
|
|
514
|
+
export interface HttpClaimApi<T> {
|
|
515
|
+
/** Take a manual claim handle for multi-step work. Release it when done. */
|
|
516
|
+
(params: ClaimParams<T>): Promise<ClaimHandle<T>>;
|
|
517
|
+
/** Release a manual claim you hold. */
|
|
518
|
+
release(params: ClaimLookupParams<T> | ClaimHandle<T>): Promise<void>;
|
|
519
|
+
/**
|
|
520
|
+
* Current holder of the lease on a row, or `null` when free. For UI badges,
|
|
521
|
+
* preflight checks, and operators.
|
|
522
|
+
*/
|
|
523
|
+
state(params: ClaimLookupParams<T>): Promise<Intent | null>;
|
|
524
|
+
/**
|
|
525
|
+
* FIFO wait line behind the holder. Advanced: useful for operator UIs and
|
|
526
|
+
* schedulers.
|
|
527
|
+
*/
|
|
528
|
+
queue(params: ClaimLookupParams<T>): Promise<{
|
|
529
|
+
readonly object: 'list';
|
|
530
|
+
readonly data: readonly Intent[];
|
|
531
|
+
}>;
|
|
532
|
+
/**
|
|
533
|
+
* Re-rank the wait line. Advanced and permission-gated.
|
|
534
|
+
*/
|
|
535
|
+
reorder(params: ClaimReorderParams<T>): Promise<void>;
|
|
468
536
|
}
|
|
469
537
|
export interface ModelClient<T = Record<string, unknown>> {
|
|
470
|
-
retrieve(
|
|
471
|
-
|
|
538
|
+
retrieve(params: ModelReadOptions & {
|
|
539
|
+
readonly id: string;
|
|
540
|
+
}): Promise<ModelRead<T>>;
|
|
541
|
+
/**
|
|
542
|
+
* Collection read over HTTP (server round-trip). Equality `where`, `orderBy`,
|
|
543
|
+
* `limit`. Present on the stateless protocol client; the store-backed
|
|
544
|
+
* `.model(name)` accessor omits it (use the typed `ablo.<model>.list` there).
|
|
545
|
+
*/
|
|
546
|
+
list?(options?: ModelLoadOptions<T>): Promise<T[]>;
|
|
547
|
+
create(params: ModelMutationOptions & {
|
|
548
|
+
readonly data: Record<string, unknown>;
|
|
472
549
|
readonly id?: string | null;
|
|
473
550
|
}): Promise<CommitReceipt>;
|
|
474
|
-
update(
|
|
475
|
-
|
|
551
|
+
update(params: ModelMutationOptions & {
|
|
552
|
+
readonly id: string;
|
|
553
|
+
readonly data: Record<string, unknown>;
|
|
554
|
+
}): Promise<CommitReceipt>;
|
|
555
|
+
delete(params: ModelMutationOptions & {
|
|
556
|
+
readonly id: string;
|
|
557
|
+
}): Promise<CommitReceipt>;
|
|
558
|
+
/**
|
|
559
|
+
* Durable lease + FIFO wait-line over HTTP — coordination without a socket.
|
|
560
|
+
* Present on the stateless protocol client (`Ablo({ schema: null })` /
|
|
561
|
+
* `createAbloHttpClient`); the store-backed `.model(name)` accessor omits it
|
|
562
|
+
* (the typed `ablo.<model>.claim` proxy is the full reactive namespace there).
|
|
563
|
+
*/
|
|
564
|
+
claim?: HttpClaimApi<T>;
|
|
476
565
|
}
|
|
477
566
|
/** A single data operation a scoped **agent** session may perform on a model. */
|
|
478
567
|
export type SessionOperation = 'read' | 'create' | 'update' | 'delete';
|
|
@@ -572,8 +661,8 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
572
661
|
* offline, this waits until reconnect + flush completes.
|
|
573
662
|
*
|
|
574
663
|
* ```ts
|
|
575
|
-
* await sync.reports.create({ title: 'A' });
|
|
576
|
-
* await sync.reports.create({ title: 'B' });
|
|
664
|
+
* await sync.reports.create({ data: { title: 'A' } });
|
|
665
|
+
* await sync.reports.create({ data: { title: 'B' } });
|
|
577
666
|
* await sync.waitForFlush(); // server has both reports
|
|
578
667
|
* ```
|
|
579
668
|
*
|
|
@@ -586,11 +675,39 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
586
675
|
/**
|
|
587
676
|
* Replace the bearer auth token used for the WebSocket upgrade and HTTP
|
|
588
677
|
* requests, WITHOUT tearing down the engine. Use to push a refreshed
|
|
589
|
-
* short-lived
|
|
590
|
-
* `getToken` refresh loop calls this. Reuses the same
|
|
591
|
-
* internal capability-token refresh; safe to call before
|
|
678
|
+
* short-lived access key (the Stripe-style `ek_`/`rk_`) before it expires —
|
|
679
|
+
* `<AbloProvider>`'s `getToken` refresh loop calls this. Reuses the same
|
|
680
|
+
* rotation path as the internal capability-token refresh; safe to call before
|
|
681
|
+
* `ready()`. Also nudges a parked connection to re-probe with the new token.
|
|
592
682
|
*/
|
|
593
683
|
setAuthToken(token: string): void;
|
|
684
|
+
/**
|
|
685
|
+
* Resolve the active bearer credential this engine authenticates with — the
|
|
686
|
+
* live `ek_`/`rk_` the WebSocket and HTTP transports currently carry (kept
|
|
687
|
+
* fresh by the `getToken` refresh loop), falling back to a configured API
|
|
688
|
+
* key. Returns `null` when no credential is set yet. Use it to authenticate
|
|
689
|
+
* a side-band request to the same sync-server (e.g. the S3 presign endpoint)
|
|
690
|
+
* with the very token this client already holds — no extra mint round-trip.
|
|
691
|
+
*/
|
|
692
|
+
getAuthToken(): Promise<string | null>;
|
|
693
|
+
/**
|
|
694
|
+
* Register a re-mint hook for the short-lived access key. The connection
|
|
695
|
+
* layer calls it WHEN it finds the key stale (a `credential_stale` probe) or
|
|
696
|
+
* on an external nudge; the hook mints a fresh `ek_`/`rk_` from the still-valid
|
|
697
|
+
* login. Mirrors the `getToken` contract: resolve a token, resolve `null` when
|
|
698
|
+
* the login itself is gone (→ sign out), or THROW on a transient failure (→
|
|
699
|
+
* back off, never sign out). `<AbloProvider>` wires this from its
|
|
700
|
+
* `getToken`/`authEndpoint`. Safe to call before `ready()`.
|
|
701
|
+
*/
|
|
702
|
+
setCredentialRefresher(refresher: (() => Promise<string | null>) | null): void;
|
|
703
|
+
/**
|
|
704
|
+
* Ask the connection layer to re-probe and reconnect now, using the current
|
|
705
|
+
* credential. Idempotent and safe in any state (a no-op while connected).
|
|
706
|
+
* Call after an OS-wake signal (Electron `powerMonitor` 'resume') so a
|
|
707
|
+
* connection parked since sleep recovers immediately instead of waiting for
|
|
708
|
+
* the watchdog.
|
|
709
|
+
*/
|
|
710
|
+
nudgeReconnect(): void;
|
|
594
711
|
/**
|
|
595
712
|
* Mint a short-lived, scoped **session token** for one end user — the
|
|
596
713
|
* Stripe `ephemeralKeys.create` / Supabase session shape. Call this on YOUR
|
|
@@ -946,6 +1063,7 @@ export declare namespace Ablo {
|
|
|
946
1063
|
namespace Source {
|
|
947
1064
|
type Operation = import('../source/index.js').SourceOperation;
|
|
948
1065
|
type Event = import('../source/index.js').SourceEvent;
|
|
1066
|
+
type EventForOperationOptions = import('../source/index.js').SourceEventForOperationOptions;
|
|
949
1067
|
type EventsResult = import('../source/index.js').SourceEventsResult;
|
|
950
1068
|
type Scope = import('../source/index.js').SourceScope;
|
|
951
1069
|
type ApiKey = import('../source/index.js').SourceApiKey;
|