@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.
Files changed (162) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/README.md +32 -27
  3. package/dist/BaseSyncedStore.d.ts +73 -0
  4. package/dist/BaseSyncedStore.js +172 -2
  5. package/dist/Model.d.ts +42 -0
  6. package/dist/Model.js +103 -44
  7. package/dist/agent/session.js +3 -3
  8. package/dist/ai-sdk/coordination-context.js +4 -0
  9. package/dist/ai-sdk/index.d.ts +56 -47
  10. package/dist/ai-sdk/index.js +56 -47
  11. package/dist/ai-sdk/intent-broadcast.d.ts +5 -0
  12. package/dist/ai-sdk/intent-broadcast.js +11 -4
  13. package/dist/ai-sdk/wrap.d.ts +14 -11
  14. package/dist/ai-sdk/wrap.js +11 -13
  15. package/dist/auth/credentialSource.d.ts +34 -0
  16. package/dist/auth/credentialSource.js +63 -0
  17. package/dist/auth/index.d.ts +2 -22
  18. package/dist/auth/index.js +4 -42
  19. package/dist/auth/schemas.d.ts +35 -0
  20. package/dist/auth/schemas.js +53 -0
  21. package/dist/client/Ablo.d.ts +160 -42
  22. package/dist/client/Ablo.js +145 -75
  23. package/dist/client/ApiClient.d.ts +20 -4
  24. package/dist/client/ApiClient.js +166 -28
  25. package/dist/client/auth.d.ts +14 -5
  26. package/dist/client/auth.js +60 -7
  27. package/dist/client/createInternalComponents.d.ts +2 -0
  28. package/dist/client/createInternalComponents.js +8 -1
  29. package/dist/client/createModelProxy.d.ts +130 -66
  30. package/dist/client/createModelProxy.js +152 -49
  31. package/dist/client/httpClient.d.ts +71 -0
  32. package/dist/client/httpClient.js +69 -0
  33. package/dist/client/identity.d.ts +2 -6
  34. package/dist/client/identity.js +49 -11
  35. package/dist/client/index.d.ts +1 -0
  36. package/dist/client/index.js +1 -0
  37. package/dist/client/registerDataSource.d.ts +3 -3
  38. package/dist/client/registerDataSource.js +11 -9
  39. package/dist/client/validateAbloOptions.js +1 -1
  40. package/dist/core/DatabaseManager.js +30 -2
  41. package/dist/core/openIDBWithTimeout.d.ts +36 -0
  42. package/dist/core/openIDBWithTimeout.js +88 -1
  43. package/dist/errorCodes.d.ts +70 -1
  44. package/dist/errorCodes.js +108 -9
  45. package/dist/errors.d.ts +2 -2
  46. package/dist/errors.js +72 -22
  47. package/dist/index.d.ts +17 -8
  48. package/dist/index.js +15 -6
  49. package/dist/keys/index.d.ts +16 -1
  50. package/dist/keys/index.js +26 -6
  51. package/dist/mutators/UndoManager.d.ts +86 -50
  52. package/dist/mutators/UndoManager.js +129 -22
  53. package/dist/mutators/inverseOp.d.ts +129 -0
  54. package/dist/mutators/inverseOp.js +74 -0
  55. package/dist/mutators/readerActions.d.ts +1 -1
  56. package/dist/mutators/undoApply.d.ts +42 -0
  57. package/dist/mutators/undoApply.js +143 -0
  58. package/dist/query/client.d.ts +10 -9
  59. package/dist/query/client.js +3 -6
  60. package/dist/react/AbloProvider.d.ts +23 -126
  61. package/dist/react/AbloProvider.js +62 -199
  62. package/dist/react/useAblo.d.ts +2 -2
  63. package/dist/react/useCurrentUserId.d.ts +1 -1
  64. package/dist/react/useCurrentUserId.js +1 -1
  65. package/dist/react/useMutators.js +19 -12
  66. package/dist/schema/ddl.d.ts +26 -3
  67. package/dist/schema/ddl.js +152 -4
  68. package/dist/schema/index.d.ts +4 -0
  69. package/dist/schema/index.js +12 -0
  70. package/dist/schema/model.d.ts +11 -0
  71. package/dist/schema/model.js +2 -0
  72. package/dist/schema/openapi.d.ts +28 -0
  73. package/dist/schema/openapi.js +118 -0
  74. package/dist/schema/plane.d.ts +23 -0
  75. package/dist/schema/plane.js +19 -0
  76. package/dist/schema/relation.d.ts +20 -0
  77. package/dist/schema/serialize.d.ts +4 -0
  78. package/dist/schema/serialize.js +4 -0
  79. package/dist/schema/sync-delta-row.d.ts +157 -0
  80. package/dist/schema/sync-delta-row.js +102 -0
  81. package/dist/schema/sync-delta-wire.d.ts +180 -0
  82. package/dist/schema/sync-delta-wire.js +102 -0
  83. package/dist/server/adapter.d.ts +156 -0
  84. package/dist/server/adapter.js +19 -0
  85. package/dist/server/commit.d.ts +82 -0
  86. package/dist/server/commit.js +1 -0
  87. package/dist/server/index.d.ts +14 -0
  88. package/dist/server/index.js +1 -0
  89. package/dist/server/next.d.ts +51 -0
  90. package/dist/server/next.js +47 -0
  91. package/dist/server/read-config.d.ts +60 -0
  92. package/dist/server/read-config.js +8 -0
  93. package/dist/server/storage-mode.d.ts +17 -0
  94. package/dist/server/storage-mode.js +12 -0
  95. package/dist/source/adapter.d.ts +59 -0
  96. package/dist/source/adapter.js +19 -0
  97. package/dist/source/adapters/drizzle.d.ts +34 -0
  98. package/dist/source/adapters/drizzle.js +147 -0
  99. package/dist/source/adapters/memory.d.ts +12 -0
  100. package/dist/source/adapters/memory.js +114 -0
  101. package/dist/source/adapters/prisma.d.ts +57 -0
  102. package/dist/source/adapters/prisma.js +199 -0
  103. package/dist/source/conformance.d.ts +32 -0
  104. package/dist/source/conformance.js +134 -0
  105. package/dist/source/contract.d.ts +143 -0
  106. package/dist/source/contract.js +98 -0
  107. package/dist/source/index.d.ts +61 -10
  108. package/dist/source/index.js +98 -0
  109. package/dist/source/next.d.ts +33 -0
  110. package/dist/source/next.js +26 -0
  111. package/dist/sync/BootstrapHelper.d.ts +10 -0
  112. package/dist/sync/BootstrapHelper.js +10 -15
  113. package/dist/sync/ConnectionManager.d.ts +55 -1
  114. package/dist/sync/ConnectionManager.js +155 -16
  115. package/dist/sync/HydrationCoordinator.d.ts +93 -17
  116. package/dist/sync/HydrationCoordinator.js +238 -39
  117. package/dist/sync/NetworkProbe.d.ts +58 -24
  118. package/dist/sync/NetworkProbe.js +118 -42
  119. package/dist/sync/SyncWebSocket.d.ts +45 -70
  120. package/dist/sync/SyncWebSocket.js +70 -36
  121. package/dist/sync/createIntentStream.js +10 -1
  122. package/dist/types/streams.d.ts +9 -0
  123. package/dist/utils/mobx-setup.js +1 -0
  124. package/dist/webhooks/events.d.ts +38 -0
  125. package/dist/webhooks/events.js +40 -0
  126. package/dist/webhooks/index.d.ts +10 -0
  127. package/dist/webhooks/index.js +10 -0
  128. package/dist/wire/errorEnvelope.d.ts +34 -0
  129. package/dist/wire/errorEnvelope.js +86 -0
  130. package/dist/wire/frames.d.ts +119 -0
  131. package/dist/wire/frames.js +1 -0
  132. package/dist/wire/index.d.ts +24 -0
  133. package/dist/wire/index.js +21 -0
  134. package/dist/wire/listEnvelope.d.ts +45 -0
  135. package/dist/wire/listEnvelope.js +17 -0
  136. package/docs/api.md +47 -44
  137. package/docs/cli.md +44 -44
  138. package/docs/client-behavior.md +30 -30
  139. package/docs/coordination.md +33 -36
  140. package/docs/data-sources.md +35 -15
  141. package/docs/examples/agent-human.md +45 -43
  142. package/docs/examples/ai-sdk-tool.md +20 -16
  143. package/docs/examples/existing-python-backend.md +16 -12
  144. package/docs/examples/nextjs.md +14 -12
  145. package/docs/examples/scoped-agent.md +1 -1
  146. package/docs/examples/server-agent.md +24 -21
  147. package/docs/guarantees.md +15 -13
  148. package/docs/index.md +1 -1
  149. package/docs/integration-guide.md +30 -30
  150. package/docs/interaction-model.md +19 -23
  151. package/docs/mcp/claude-code.md +3 -3
  152. package/docs/mcp/cursor.md +1 -1
  153. package/docs/mcp/windsurf.md +2 -2
  154. package/docs/mcp.md +6 -6
  155. package/docs/quickstart.md +41 -31
  156. package/docs/react.md +13 -9
  157. package/docs/schema-contract.md +12 -10
  158. package/docs/the-loop.md +21 -0
  159. package/examples/data-source/README.md +4 -5
  160. package/examples/data-source/customer-server.ts +27 -25
  161. package/llms.txt +28 -5
  162. 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
+ }
@@ -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
- /** Server response shape matches Phase 1A + 1B wire output. */
15
- export interface CapabilityExchangeResponse {
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;
@@ -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 { AbloAuthenticationError, translateHttpError } from '../errors.js';
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
- const raw = (await response.json());
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
+ }
@@ -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(reportId, { status: 'ready' });
16
- * await sync.reports.delete(reportId);
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.ablo.finance — full option reference
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 used for authentication.
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 (`sk_live_...`) or an async function that
87
- * resolves to one. Defaults to `process.env['ABLO_API_KEY']`, so you
88
- * usually don't pass this explicitly server-side.
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
- * Connection string to YOUR OWN Postgres. When set, Ablo registers this
93
- * database as your project's data store and writes synced rows back into it
94
- * (dedicated/BYO tenant), so your data stays canonical in your DB while Ablo
95
- * runs the sync/coordination plane. Defaults to `process.env['DATABASE_URL']`.
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. Provide Ablo a NON-superuser, non-`BYPASSRLS` role: the server runs
100
- * the tenant plane with row-level security forced, and rejects a privileged
101
- * role that couldn't enforce isolation.
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://mesh.ablo.finance` for hosted production; pass an explicit
163
- * URL for self-hosted or staging (e.g. `wss://mesh-staging.ablo.finance`).
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` with
293
- * `credentials: 'include'` (cookie auth) when no `apiKey` is set.
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 entity by id (sync, from local pool)
340
- * `list({where})` — collection with filter (sync, from local pool)
341
- * `count({where})` count (sync, from local pool)
342
- * `load({where})` async hydrate through pool IDB network
343
- * `create / update / delete` — optimistic writes
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, ClaimedRow, ModelOperations, } from './createModelProxy.js';
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(id: string, options?: ModelReadOptions): Promise<ModelRead<T>>;
471
- create(data: Record<string, unknown>, options?: ModelMutationOptions & {
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(id: string, data: Record<string, unknown>, options?: ModelMutationOptions): Promise<CommitReceipt>;
475
- delete(id: string, options?: ModelMutationOptions): Promise<CommitReceipt>;
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 token (e.g. a 15m JWT) before it expires — `<AbloProvider>`'s
590
- * `getToken` refresh loop calls this. Reuses the same rotation path as the
591
- * internal capability-token refresh; safe to call before `ready()`.
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;