@abloatai/ablo 0.24.0 → 0.26.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 (246) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +33 -0
  3. package/README.md +45 -36
  4. package/dist/BaseSyncedStore.d.ts +72 -195
  5. package/dist/BaseSyncedStore.js +240 -625
  6. package/dist/Database.d.ts +8 -9
  7. package/dist/Database.js +9 -14
  8. package/dist/LazyReferenceCollection.js +5 -0
  9. package/dist/Model.js +6 -8
  10. package/dist/ModelRegistry.d.ts +25 -6
  11. package/dist/ModelRegistry.js +12 -6
  12. package/dist/NetworkMonitor.js +1 -1
  13. package/dist/ObjectPool.js +9 -3
  14. package/dist/SyncClient.d.ts +15 -5
  15. package/dist/SyncClient.js +152 -66
  16. package/dist/SyncEngineContext.d.ts +1 -3
  17. package/dist/SyncEngineContext.js +1 -2
  18. package/dist/agent/Agent.d.ts +21 -8
  19. package/dist/agent/Agent.js +26 -13
  20. package/dist/ai-sdk/coordination-context.js +9 -3
  21. package/dist/ai-sdk/wrap.d.ts +1 -1
  22. package/dist/ai-sdk/wrap.js +1 -1
  23. package/dist/auth/credentialPolicy.d.ts +10 -2
  24. package/dist/auth/credentialSource.d.ts +1 -13
  25. package/dist/auth/credentialSource.js +6 -13
  26. package/dist/auth/index.js +3 -3
  27. package/dist/cli.cjs +583 -287
  28. package/dist/client/Ablo.d.ts +35 -764
  29. package/dist/client/Ablo.js +64 -688
  30. package/dist/client/ApiClient.d.ts +14 -1
  31. package/dist/client/ApiClient.js +63 -9
  32. package/dist/client/auth.d.ts +8 -17
  33. package/dist/client/auth.js +42 -6
  34. package/dist/client/consoleLogger.d.ts +36 -0
  35. package/dist/client/consoleLogger.js +45 -0
  36. package/dist/client/createInternalComponents.js +1 -1
  37. package/dist/client/createModelProxy.js +10 -6
  38. package/dist/client/credentialEndpoint.d.ts +63 -0
  39. package/dist/client/credentialEndpoint.js +87 -0
  40. package/dist/client/hostedEndpoints.d.ts +24 -0
  41. package/dist/client/hostedEndpoints.js +24 -0
  42. package/dist/client/httpClient.d.ts +1 -1
  43. package/dist/client/identity.js +2 -1
  44. package/dist/client/modelRegistration.d.ts +14 -0
  45. package/dist/client/modelRegistration.js +321 -0
  46. package/dist/client/options.d.ts +396 -0
  47. package/dist/client/options.js +10 -0
  48. package/dist/client/resourceTypes.d.ts +344 -0
  49. package/dist/client/resourceTypes.js +13 -0
  50. package/dist/client/schemaConfig.d.ts +56 -0
  51. package/dist/client/schemaConfig.js +188 -0
  52. package/dist/client/sessionMint.d.ts +1 -1
  53. package/dist/client/validateAbloOptions.js +1 -1
  54. package/dist/client/wsMutationExecutor.d.ts +33 -0
  55. package/dist/client/wsMutationExecutor.js +77 -0
  56. package/dist/context.js +0 -3
  57. package/dist/coordination/index.d.ts +8 -1
  58. package/dist/coordination/index.js +24 -1
  59. package/dist/coordination/trace.d.ts +0 -1
  60. package/dist/coordination/trace.js +0 -1
  61. package/dist/core/DatabaseManager.d.ts +0 -1
  62. package/dist/core/DatabaseManager.js +26 -24
  63. package/dist/core/QueryProcessor.js +7 -8
  64. package/dist/core/QueryView.d.ts +14 -2
  65. package/dist/core/QueryView.js +4 -5
  66. package/dist/core/StoreManager.d.ts +2 -2
  67. package/dist/core/StoreManager.js +5 -1
  68. package/dist/core/index.d.ts +1 -1
  69. package/dist/core/openIDBWithTimeout.js +15 -11
  70. package/dist/core/query-utils.js +1 -1
  71. package/dist/core/storeContract.d.ts +143 -0
  72. package/dist/core/storeContract.js +16 -0
  73. package/dist/errorCodes.d.ts +11 -2
  74. package/dist/errorCodes.js +155 -137
  75. package/dist/errors.d.ts +18 -7
  76. package/dist/errors.js +14 -1
  77. package/dist/index.js +5 -0
  78. package/dist/interfaces/index.d.ts +6 -20
  79. package/dist/keys/index.js +18 -9
  80. package/dist/mutators/UndoManager.js +6 -1
  81. package/dist/mutators/defineMutators.d.ts +1 -3
  82. package/dist/policy/types.d.ts +1 -1
  83. package/dist/query/client.d.ts +11 -0
  84. package/dist/query/client.js +81 -52
  85. package/dist/react/AbloProvider.d.ts +6 -10
  86. package/dist/react/AbloProvider.js +22 -15
  87. package/dist/react/context.d.ts +2 -118
  88. package/dist/react/useAblo.js +1 -1
  89. package/dist/react/useErrorListener.js +1 -1
  90. package/dist/react/useMutationFailureListener.js +1 -1
  91. package/dist/react/useMutators.js +7 -1
  92. package/dist/react/useReactive.js +0 -1
  93. package/dist/react/useUndoScope.js +2 -2
  94. package/dist/schema/ddlLock.d.ts +39 -0
  95. package/dist/schema/ddlLock.js +51 -0
  96. package/dist/schema/field.js +1 -1
  97. package/dist/schema/index.d.ts +4 -3
  98. package/dist/schema/index.js +15 -5
  99. package/dist/schema/model.d.ts +3 -3
  100. package/dist/schema/model.js +2 -2
  101. package/dist/schema/openapi.js +5 -1
  102. package/dist/schema/queries.d.ts +1 -1
  103. package/dist/schema/queries.js +1 -0
  104. package/dist/schema/residency.d.ts +35 -0
  105. package/dist/schema/residency.js +27 -0
  106. package/dist/schema/roles.d.ts +25 -0
  107. package/dist/schema/roles.js +37 -0
  108. package/dist/schema/schema.d.ts +3 -3
  109. package/dist/schema/schema.js +11 -7
  110. package/dist/schema/serialize.d.ts +8 -8
  111. package/dist/schema/sync-delta-row.d.ts +2 -2
  112. package/dist/schema/sync-delta-row.js +2 -2
  113. package/dist/server/storage-mode.d.ts +8 -1
  114. package/dist/server/storage-mode.js +14 -4
  115. package/dist/source/adapter.d.ts +1 -1
  116. package/dist/source/adapters/drizzle.js +4 -3
  117. package/dist/source/adapters/kysely.js +4 -3
  118. package/dist/source/adapters/memory.js +1 -1
  119. package/dist/source/adapters/prisma.js +5 -4
  120. package/dist/source/conformance.js +10 -8
  121. package/dist/source/connector.js +4 -2
  122. package/dist/source/factory.d.ts +106 -0
  123. package/dist/source/factory.js +273 -0
  124. package/dist/source/index.d.ts +11 -461
  125. package/dist/source/index.js +14 -416
  126. package/dist/source/next.d.ts +1 -1
  127. package/dist/source/next.js +1 -1
  128. package/dist/source/pushQueue.d.ts +1 -1
  129. package/dist/source/pushQueue.js +6 -3
  130. package/dist/source/signing.d.ts +59 -0
  131. package/dist/source/signing.js +142 -0
  132. package/dist/source/types.d.ts +334 -0
  133. package/dist/source/types.js +44 -0
  134. package/dist/stores/ObjectStore.d.ts +1 -1
  135. package/dist/stores/ObjectStore.js +23 -23
  136. package/dist/stores/SyncActionStore.d.ts +1 -1
  137. package/dist/stores/SyncActionStore.js +70 -35
  138. package/dist/surface.d.ts +1 -1
  139. package/dist/surface.js +1 -0
  140. package/dist/sync/AreaOfInterestManager.d.ts +2 -2
  141. package/dist/sync/AreaOfInterestManager.js +1 -1
  142. package/dist/sync/BootstrapHelper.d.ts +1 -3
  143. package/dist/sync/BootstrapHelper.js +29 -8
  144. package/dist/sync/ConnectionManager.d.ts +8 -0
  145. package/dist/sync/ConnectionManager.js +19 -4
  146. package/dist/sync/HydrationCoordinator.d.ts +12 -0
  147. package/dist/sync/HydrationCoordinator.js +25 -5
  148. package/dist/sync/NetworkProbe.d.ts +3 -2
  149. package/dist/sync/NetworkProbe.js +7 -6
  150. package/dist/sync/SyncWebSocket.d.ts +71 -120
  151. package/dist/sync/SyncWebSocket.js +324 -630
  152. package/dist/sync/awaitClaimGrant.js +27 -19
  153. package/dist/sync/bootstrapApply.d.ts +60 -0
  154. package/dist/sync/bootstrapApply.js +65 -0
  155. package/dist/sync/commitFrames.d.ts +43 -0
  156. package/dist/sync/commitFrames.js +94 -0
  157. package/dist/sync/createClaimStream.js +3 -2
  158. package/dist/sync/createSnapshot.d.ts +1 -3
  159. package/dist/sync/credentialLifecycle.d.ts +175 -0
  160. package/dist/sync/credentialLifecycle.js +329 -0
  161. package/dist/sync/deltaPipeline.d.ts +111 -0
  162. package/dist/sync/deltaPipeline.js +262 -0
  163. package/dist/sync/groupChange.d.ts +111 -0
  164. package/dist/sync/groupChange.js +246 -0
  165. package/dist/sync/heartbeat.d.ts +62 -0
  166. package/dist/sync/heartbeat.js +91 -0
  167. package/dist/sync/participants.d.ts +8 -8
  168. package/dist/sync/syncCursor.d.ts +44 -0
  169. package/dist/sync/syncCursor.js +59 -0
  170. package/dist/sync/syncPlan.d.ts +62 -0
  171. package/dist/sync/syncPlan.js +51 -0
  172. package/dist/sync/wsFrameHandlers.d.ts +117 -0
  173. package/dist/sync/wsFrameHandlers.js +378 -0
  174. package/dist/testing/fixtures/bootstrap.d.ts +9 -11
  175. package/dist/testing/fixtures/deltas.d.ts +1 -1
  176. package/dist/testing/fixtures/models.js +1 -0
  177. package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
  178. package/dist/testing/helpers/sync-engine-harness.js +2 -1
  179. package/dist/testing/index.d.ts +1 -1
  180. package/dist/testing/index.js +1 -1
  181. package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
  182. package/dist/testing/mocks/MockSyncContext.js +1 -32
  183. package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
  184. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  185. package/dist/transactions/TransactionQueue.d.ts +20 -56
  186. package/dist/transactions/TransactionQueue.js +101 -487
  187. package/dist/transactions/TransactionStore.d.ts +18 -0
  188. package/dist/transactions/TransactionStore.js +51 -0
  189. package/dist/transactions/coalesceRules.d.ts +34 -0
  190. package/dist/transactions/coalesceRules.js +117 -0
  191. package/dist/transactions/commitPayload.d.ts +134 -0
  192. package/dist/transactions/commitPayload.js +152 -0
  193. package/dist/transactions/deltaConfirmation.d.ts +60 -0
  194. package/dist/transactions/deltaConfirmation.js +223 -0
  195. package/dist/transactions/optimistic.d.ts +24 -0
  196. package/dist/transactions/optimistic.js +45 -0
  197. package/dist/transactions/persistedReplay.d.ts +93 -0
  198. package/dist/transactions/persistedReplay.js +105 -0
  199. package/dist/types/index.d.ts +3 -3
  200. package/dist/types/index.js +0 -5
  201. package/dist/types/modelData.d.ts +12 -0
  202. package/dist/types/modelData.js +11 -0
  203. package/dist/types/participant.d.ts +21 -0
  204. package/dist/types/participant.js +12 -0
  205. package/dist/types/streams.d.ts +8 -14
  206. package/dist/utils/mobx-setup.d.ts +1 -1
  207. package/dist/utils/mobx-setup.js +2 -2
  208. package/dist/wire/errorEnvelope.d.ts +11 -2
  209. package/dist/wire/errorEnvelope.js +14 -2
  210. package/dist/wire/frames.d.ts +90 -0
  211. package/dist/wire/frames.js +55 -1
  212. package/dist/wire/index.d.ts +4 -1
  213. package/dist/wire/index.js +20 -1
  214. package/dist/wire/protocol.d.ts +45 -0
  215. package/dist/wire/protocol.js +45 -0
  216. package/dist/wire/protocolVersion.d.ts +56 -0
  217. package/dist/wire/protocolVersion.js +63 -0
  218. package/docs/api-keys.md +4 -3
  219. package/docs/data-sources.md +12 -5
  220. package/docs/examples/existing-python-backend.md +3 -3
  221. package/docs/identity.md +4 -4
  222. package/docs/integration-guide.md +1 -1
  223. package/docs/react.md +1 -1
  224. package/docs/sessions.md +5 -7
  225. package/llms.txt +4 -2
  226. package/package.json +15 -13
  227. package/dist/client/index.d.ts +0 -36
  228. package/dist/client/index.js +0 -33
  229. package/dist/config/index.d.ts +0 -10
  230. package/dist/config/index.js +0 -12
  231. package/dist/interfaces/headless.d.ts +0 -95
  232. package/dist/interfaces/headless.js +0 -41
  233. package/dist/query/index.d.ts +0 -6
  234. package/dist/query/index.js +0 -5
  235. package/dist/realtime/index.d.ts +0 -10
  236. package/dist/realtime/index.js +0 -9
  237. package/dist/schema/plane.d.ts +0 -23
  238. package/dist/schema/plane.js +0 -19
  239. package/dist/server/next.d.ts +0 -51
  240. package/dist/server/next.js +0 -47
  241. package/dist/sync/OfflineFlush.d.ts +0 -9
  242. package/dist/sync/OfflineFlush.js +0 -22
  243. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  244. package/dist/sync/OfflineTransactionStore.js +0 -263
  245. package/dist/transactions/index.d.ts +0 -16
  246. package/dist/transactions/index.js +0 -7
@@ -5,7 +5,8 @@
5
5
  * IndexedDB, no WebSocket. It maps the public Model / Claim / Commit
6
6
  * nouns directly to HTTP routes on sync-server.
7
7
  */
8
- import type { AbloOptions, CommitResource, ClaimCreateOptions, ClaimWaitOptions, ModelClient, ModelClaim, ModelTarget, CreateSessionParams, AbloSession } from './Ablo.js';
8
+ import type { AbloOptions } from './options.js';
9
+ import type { CommitResource, ClaimCreateOptions, ClaimWaitOptions, ModelClient, ModelClaim, ModelTarget, CreateSessionParams, AbloSession } from './resourceTypes.js';
9
10
  import type { SchemaRecord } from '../schema/schema.js';
10
11
  import type { Duration } from '../utils/duration.js';
11
12
  import type { Claim } from '../types/streams.js';
@@ -19,7 +20,19 @@ export type AbloApiClientOptions = Omit<AbloOptions, 'schema'> & {
19
20
  * `ClaimLog` works identically for headless (server-agent) evals.
20
21
  */
21
22
  readonly observability?: SyncObservabilityProvider;
23
+ /**
24
+ * Per-request deadline in milliseconds for the stateless HTTP transport.
25
+ * Every request this client issues is aborted after this long and surfaces
26
+ * as a retryable connection error — without it a black-holed server hangs
27
+ * a headless agent forever (browsers never time fetch out on their own).
28
+ * Pass `0` to disable the deadline.
29
+ *
30
+ * @default 30_000
31
+ */
32
+ readonly timeoutMs?: number;
22
33
  };
34
+ /** Default per-request deadline for the stateless HTTP transport. */
35
+ export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
23
36
  export interface AbloApiClaims {
24
37
  create(options: ClaimCreateOptions): Promise<Claim>;
25
38
  list(target?: Partial<ModelTarget>): Promise<readonly ModelClaim[]>;
@@ -9,9 +9,12 @@ import { AbloClaimedError, AbloAuthenticationError, AbloConnectionError, AbloVal
9
9
  import { reconcileFunctionalUpdate, } from './functionalUpdate.js';
10
10
  import { assertBrowserSafety, readProcessEnv, resolveApiKey, resolveApiKeyValue, resolveAuthToken, resolveBaseURL, resolveBootstrapBaseUrl, resolveDatabaseUrl, warnIfCliKeyMismatch, warnIfDatabaseUrlEnvIgnored, warnIfDatabaseUrlDeprecated, } from './auth.js';
11
11
  import { registerDataSource } from './registerDataSource.js';
12
+ import { PROTOCOL_VERSION, PROTOCOL_VERSION_HEADER } from '../wire/protocolVersion.js';
12
13
  import { toSeconds } from '../utils/duration.js';
13
14
  import { mintSession } from './sessionMint.js';
14
15
  import { assertWriteOptions } from './writeOptionsSchema.js';
16
+ /** Default per-request deadline for the stateless HTTP transport. */
17
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
15
18
  const DEFAULT_AGENT_LEASE = '10m';
16
19
  export function createProtocolClient(options) {
17
20
  const env = readProcessEnv();
@@ -101,6 +104,9 @@ export function createProtocolClient(options) {
101
104
  const headers = {
102
105
  'Content-Type': 'application/json',
103
106
  Authorization: `Bearer ${token}`,
107
+ // Protocol handshake for the HTTP transport (wire/protocolVersion.ts):
108
+ // the server answers an out-of-range version with a typed 426.
109
+ [PROTOCOL_VERSION_HEADER]: String(PROTOCOL_VERSION),
104
110
  };
105
111
  for (const [key, value] of Object.entries(options.defaultHeaders ?? {})) {
106
112
  if (value == null) {
@@ -120,20 +126,66 @@ export function createProtocolClient(options) {
120
126
  }
121
127
  return target.toString();
122
128
  }
129
+ const requestTimeoutMs = options.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
123
130
  async function requestJson(path, init) {
124
131
  await ready();
125
132
  const { idempotencyKey, ...requestInit } = init;
126
133
  const headers = await authHeaders();
127
134
  if (idempotencyKey)
128
135
  headers['Idempotency-Key'] = idempotencyKey;
129
- const res = await fetchImpl(endpoint(path), {
130
- ...requestInit,
131
- headers: {
132
- ...headers,
133
- ...requestInit.headers,
134
- },
135
- });
136
- const bodyText = await res.text();
136
+ // Deadline: abort the request after `timeoutMs` so a black-holed server
137
+ // can't hang the caller forever (fetch has NO default timeout in browsers,
138
+ // and only undici's generous defaults in Node). A caller-supplied signal
139
+ // is combined with the deadline via a shared controller — the portable
140
+ // equivalent of `AbortSignal.any([caller, AbortSignal.timeout(t)])`,
141
+ // which older runtimes (and the jsdom test env) don't implement. The
142
+ // same pattern already guards `query/client.ts` and `BootstrapHelper`.
143
+ const callerSignal = requestInit.signal ?? undefined;
144
+ const controller = new AbortController();
145
+ const onCallerAbort = () => { controller.abort(callerSignal?.reason); };
146
+ if (callerSignal) {
147
+ if (callerSignal.aborted)
148
+ onCallerAbort();
149
+ else
150
+ callerSignal.addEventListener('abort', onCallerAbort, { once: true });
151
+ }
152
+ let timedOut = false;
153
+ const deadline = requestTimeoutMs > 0
154
+ ? setTimeout(() => {
155
+ timedOut = true;
156
+ controller.abort();
157
+ }, requestTimeoutMs)
158
+ : null;
159
+ let res;
160
+ let bodyText;
161
+ try {
162
+ res = await fetchImpl(endpoint(path), {
163
+ ...requestInit,
164
+ signal: controller.signal,
165
+ headers: {
166
+ ...headers,
167
+ ...requestInit.headers,
168
+ },
169
+ });
170
+ // Keep the deadline armed while the body streams — a server that sends
171
+ // headers then stalls the body is the same hang with better manners.
172
+ bodyText = await res.text();
173
+ }
174
+ catch (error) {
175
+ if (timedOut) {
176
+ // Retryable by contract: `wait_for_timeout` is a registered transient
177
+ // transport code, so `isRetryableCode` steers callers to retry.
178
+ throw new AbloConnectionError(`The Ablo API did not respond within ${requestTimeoutMs}ms ` +
179
+ `(${requestInit.method ?? 'GET'} ${path}). The request was aborted; ` +
180
+ 'it is safe to retry.', { code: 'wait_for_timeout', cause: error });
181
+ }
182
+ throw error;
183
+ }
184
+ finally {
185
+ if (deadline)
186
+ clearTimeout(deadline);
187
+ callerSignal?.removeEventListener('abort', onCallerAbort);
188
+ }
137
189
  const body = parseBody(bodyText);
138
190
  if (!res.ok) {
139
191
  throw translateHttpError(res.status, body ?? `Ablo API request failed: ${res.status} ${res.statusText}`, res.headers.get('x-request-id') ?? undefined);
@@ -617,7 +669,9 @@ export function createProtocolClient(options) {
617
669
  throw new AbloClaimedError(`Target ${name}/${params.id} is held; queued at position ${body.position ?? 0}. ` +
618
670
  `The HTTP client cannot await the grant without a WebSocket.`, { code: 'claim_queued' });
619
671
  }
620
- return body.claim?.id ?? body.id ?? body.id ?? createClaimId();
672
+ // `claimId` is the queued-response field name (routes/claims.ts) — a
673
+ // pre-rename regression duplicated the `body.id` arm and dropped it.
674
+ return body.claim?.id ?? body.id ?? body.claimId ?? createClaimId();
621
675
  };
622
676
  const releaseClaim = (params) => requestJson(claimPath(isClaimHandle(params) ? params.target.id : params.id), { method: 'DELETE' }).then(() => undefined);
623
677
  async function claimImpl(params) {
@@ -12,20 +12,13 @@
12
12
  * explicit options so generated apps do not accrete hidden env knobs.
13
13
  */
14
14
  /**
15
- * Async callable that resolves the current credential. Mirrors the shape
16
- * Anthropic / OpenAI / Stripe shipused for credential rotation
17
- * (e.g. AWS STS, GCP IAM, Vault) AND the short-lived per-user browser
18
- * path (mint a fresh `ek_`/`rk_` from the signed-in session). Re-exported
19
- * from `./Ablo` so existing import paths work; defined here so this module
20
- * has no circular dependency back to `Ablo.ts`.
21
- *
22
- * Contract: resolve a token; resolve `null` when the login itself is gone
23
- * (terminal → the credential lifecycle treats this as `session_expired` and
24
- * signs out); or THROW on a transient failure (→ back off and retry, never
25
- * sign out). A long-lived static `apiKey` string needs none of this — it is
26
- * used as-is. This is the single credential resolver the SDK supports.
15
+ * The credential-resolver callable contract. Defined in the `credentialEndpoint`
16
+ * leaf (which produces one) and re-exported here the historical home — so
17
+ * `./auth` importers keep working while the module graph stays one-directional
18
+ * (`auth credentialEndpoint`, never back). See there for the full contract.
27
19
  */
28
- export type ApiKeySetter = () => Promise<string | null>;
20
+ import type { ApiKeySetter } from './credentialEndpoint.js';
21
+ export type { ApiKeySetter };
29
22
  export interface AuthResolveInput {
30
23
  /**
31
24
  * The full options bag the caller passed to `Ablo()`. Resolvers
@@ -34,6 +27,7 @@ export interface AuthResolveInput {
34
27
  */
35
28
  readonly options: {
36
29
  readonly apiKey?: string | ApiKeySetter | null;
30
+ readonly authEndpoint?: string | ApiKeySetter | null;
37
31
  readonly authToken?: string | null;
38
32
  readonly baseURL?: string | null;
39
33
  readonly databaseUrl?: string | null;
@@ -88,9 +82,7 @@ export declare function resolveDatabaseUrl(input: AuthResolveInput): string | nu
88
82
  export declare function warnIfDatabaseUrlEnvIgnored(input: AuthResolveInput, warn?: (message: string) => void): void;
89
83
  export declare function warnIfDatabaseUrlDeprecated(input: AuthResolveInput, warn?: (message: string) => void): void;
90
84
  export declare function warnIfCliKeyMismatch(input: AuthResolveInput, warn?: (message: string) => void): Promise<void>;
91
- export declare const ABLO_HOSTED_API_DOMAIN = "api.abloatai.com";
92
- export declare const ABLO_HOSTED_HTTP_BASE_URL = "https://api.abloatai.com";
93
- export declare const ABLO_DEFAULT_BASE_URL = "https://api.abloatai.com";
85
+ export { ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, ABLO_DEFAULT_BASE_URL } from './hostedEndpoints.js';
94
86
  /**
95
87
  * Normalize old hosted aliases to the public API domain. Self-hosted/custom
96
88
  * URLs pass through unchanged; only first-party legacy hosts are rewritten.
@@ -133,4 +125,3 @@ export declare function resolveBootstrapBaseUrl(input: {
133
125
  readonly url: string;
134
126
  readonly bootstrapBaseUrl?: string;
135
127
  }): string;
136
- export {};
@@ -11,8 +11,10 @@
11
11
  * is the only environment fallback. Other routing/auth overrides are
12
12
  * explicit options so generated apps do not accrete hidden env knobs.
13
13
  */
14
- import { AbloAuthenticationError } from '../errors.js';
14
+ import { AbloAuthenticationError, AbloValidationError } from '../errors.js';
15
15
  import { classifyCredentialKind } from '../auth/credentialPolicy.js';
16
+ import { ABLO_HOSTED_API_DOMAIN, ABLO_DEFAULT_BASE_URL } from './hostedEndpoints.js';
17
+ import { isCredentialEndpoint, createEndpointCredentialResolver } from './credentialEndpoint.js';
16
18
  /**
17
19
  * Read `process.env` defensively. Works in browser (where `process`
18
20
  * is undefined), Node, and edge runtimes that expose a partial
@@ -23,7 +25,37 @@ export function readProcessEnv() {
23
25
  return maybeGlobal.process?.env ?? {};
24
26
  }
25
27
  export function resolveApiKey(input) {
26
- return input.options.apiKey ?? input.env.ABLO_API_KEY ?? null;
28
+ // `authEndpoint` the NAMED session-mint field (Liveblocks `authEndpoint` /
29
+ // Ably `authUrl`): a route string the SDK exchanges for a short-lived token,
30
+ // or an async resolver for custom exchanges. Resolved HERE — the one
31
+ // resolution point all three factory branches (WS, `transport: 'http'`,
32
+ // protocol client) share — into an `ApiKeySetter`, so every downstream
33
+ // consumer sees the familiar resolver and the credential lifecycle drives
34
+ // renewal off it.
35
+ const endpoint = input.options.authEndpoint;
36
+ const configured = input.options.apiKey;
37
+ if (endpoint != null) {
38
+ if (configured != null) {
39
+ throw new AbloValidationError('Ablo: pass either `apiKey` (a key the process holds) or `authEndpoint` ' +
40
+ '(a route that mints the token) — not both; the client cannot know ' +
41
+ 'which credential to use.', { code: 'invalid_options', param: 'authEndpoint' });
42
+ }
43
+ if (typeof endpoint === 'function')
44
+ return endpoint;
45
+ if (!isCredentialEndpoint(endpoint)) {
46
+ throw new AbloValidationError('`authEndpoint` expects a URL or path (e.g. \'/api/ablo-session\') or an ' +
47
+ 'async resolver — a key string belongs in `apiKey`.', { code: 'invalid_options', param: 'authEndpoint' });
48
+ }
49
+ return createEndpointCredentialResolver(endpoint);
50
+ }
51
+ // `apiKey` also accepts the endpoint-string shape directly (same detection —
52
+ // key strings are `sk_`/`ek_`/`rk_`-prefixed so the shapes can't collide).
53
+ // Deliberately only the EXPLICIT option: an `ABLO_API_KEY` env value is
54
+ // always a literal key, never endpoint-detected.
55
+ if (typeof configured === 'string' && isCredentialEndpoint(configured)) {
56
+ return createEndpointCredentialResolver(configured);
57
+ }
58
+ return configured ?? input.env.ABLO_API_KEY ?? null;
27
59
  }
28
60
  export function resolveAuthToken(input) {
29
61
  return input.options.authToken ?? null;
@@ -41,6 +73,10 @@ export function modeFromApiKey(key) {
41
73
  }
42
74
  function resolveStaticApiKey(input) {
43
75
  if (typeof input.options.apiKey === 'string') {
76
+ // An endpoint-string `apiKey` is not a key — it never participates in
77
+ // CLI-mode mismatch checks (its minted tokens carry the mode instead).
78
+ if (isCredentialEndpoint(input.options.apiKey))
79
+ return null;
44
80
  return { key: input.options.apiKey, source: 'option' };
45
81
  }
46
82
  if (input.options.apiKey !== undefined && input.options.apiKey !== null) {
@@ -299,9 +335,9 @@ export async function warnIfCliKeyMismatch(input, warn) {
299
335
  else if (typeof console !== 'undefined')
300
336
  console.warn('[Ablo]', mismatch.message);
301
337
  }
302
- export const ABLO_HOSTED_API_DOMAIN = 'api.abloatai.com';
303
- export const ABLO_HOSTED_HTTP_BASE_URL = `https://${ABLO_HOSTED_API_DOMAIN}`;
304
- export const ABLO_DEFAULT_BASE_URL = `https://${ABLO_HOSTED_API_DOMAIN}`;
338
+ // Declared in the dependency-free `hostedEndpoints.ts` leaf (single source of
339
+ // the hosted domain); re-exported here so existing import paths keep working.
340
+ export { ABLO_HOSTED_API_DOMAIN, ABLO_HOSTED_HTTP_BASE_URL, ABLO_DEFAULT_BASE_URL } from './hostedEndpoints.js';
305
341
  const LEGACY_HOSTED_API_HOSTS = new Set([
306
342
  'mesh.ablo.finance',
307
343
  'mesh-staging.ablo.finance',
@@ -448,6 +484,6 @@ function ensureApiSuffix(httpBase) {
448
484
  catch {
449
485
  // Should be unreachable post-`normalizeAbloHostedBaseUrl` (which yields an
450
486
  // absolute URL), but fall back to a string check rather than throwing.
451
- return /\/api$/.test(trimmed) ? trimmed : `${trimmed}/api`;
487
+ return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
452
488
  }
453
489
  }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The default `[Ablo]` console logger + its level resolution.
3
+ *
4
+ * Extracted from `Ablo.ts`; exported so other entry points that build a
5
+ * default logger (e.g. the agent runtime) can reuse the same gating instead
6
+ * of hand-rolling a console shim.
7
+ */
8
+ import type { SyncLogger } from '../interfaces/index.js';
9
+ /**
10
+ * Level threshold for the default console logger.
11
+ *
12
+ * The SDK emits a `debug` line per model and per property during schema
13
+ * registration (see `ModelRegistry`), plus assorted lifecycle chatter. That
14
+ * is verbose by design but carries no actionable signal for app consumers, so
15
+ * the default threshold is `warn` — `debug`/`info` are dropped unless a
16
+ * consumer opts in.
17
+ *
18
+ * Opt back in with `ABLO_LOG_LEVEL=debug` (or `info`/`warn`/`error`/`silent`).
19
+ * Passing a custom `logger` to `Ablo({ logger })` bypasses this entirely.
20
+ */
21
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
22
+ /**
23
+ * Resolve the effective level. Precedence: explicit `logLevel` option →
24
+ * `debug: true` (⇒ debug) → `ABLO_LOG_LEVEL` env → default `warn`. `debug: false`
25
+ * / omitted just means "don't raise the level" — it falls through to env/default
26
+ * rather than force-silencing an ops-set env override.
27
+ */
28
+ export declare function resolveLogLevel(opts?: {
29
+ debug?: boolean;
30
+ logLevel?: LogLevel;
31
+ }): LogLevel;
32
+ /**
33
+ * Build the default logger, gated at `level` and prefixed `[Ablo]` so a creator
34
+ * with a console full of other tools' logs can see at a glance what's ours.
35
+ */
36
+ export declare function createConsoleLogger(level: LogLevel): SyncLogger;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The default `[Ablo]` console logger + its level resolution.
3
+ *
4
+ * Extracted from `Ablo.ts`; exported so other entry points that build a
5
+ * default logger (e.g. the agent runtime) can reuse the same gating instead
6
+ * of hand-rolling a console shim.
7
+ */
8
+ const LOG_LEVEL_RANK = { debug: 10, info: 20, warn: 30, error: 40, silent: 99 };
9
+ /**
10
+ * Resolve the effective level. Precedence: explicit `logLevel` option →
11
+ * `debug: true` (⇒ debug) → `ABLO_LOG_LEVEL` env → default `warn`. `debug: false`
12
+ * / omitted just means "don't raise the level" — it falls through to env/default
13
+ * rather than force-silencing an ops-set env override.
14
+ */
15
+ export function resolveLogLevel(opts) {
16
+ if (opts?.logLevel && opts.logLevel in LOG_LEVEL_RANK)
17
+ return opts.logLevel;
18
+ if (opts?.debug === true)
19
+ return 'debug';
20
+ // `globalThis.process` guard keeps this safe in browser/edge runtimes that
21
+ // have no `process` binding — there we fall through to the default.
22
+ const raw = globalThis.process?.env?.ABLO_LOG_LEVEL;
23
+ const normalized = raw?.toLowerCase();
24
+ if (normalized && normalized in LOG_LEVEL_RANK)
25
+ return normalized;
26
+ return 'warn';
27
+ }
28
+ /**
29
+ * Build the default logger, gated at `level` and prefixed `[Ablo]` so a creator
30
+ * with a console full of other tools' logs can see at a glance what's ours.
31
+ */
32
+ export function createConsoleLogger(level) {
33
+ const threshold = LOG_LEVEL_RANK[level];
34
+ const emit = (lvl, fn, args) => {
35
+ if (typeof console === 'undefined' || LOG_LEVEL_RANK[lvl] < threshold)
36
+ return;
37
+ fn('[Ablo]', ...args);
38
+ };
39
+ return {
40
+ debug: (...args) => { emit('debug', console.debug, args); },
41
+ info: (...args) => { emit('info', console.info, args); },
42
+ warn: (...args) => { emit('warn', console.warn, args); },
43
+ error: (...args) => { emit('error', console.error, args); },
44
+ };
45
+ }
@@ -62,7 +62,7 @@ export function createInternalComponents(input) {
62
62
  // WebSocket delta stream keeps hydrated rows fresh so repeat reads serve
63
63
  // pure-local with no network; after a drop, deltas may have been missed, so
64
64
  // the next read of each query must re-confirm with the server once.
65
- syncClient.on('sync:reconnecting', () => hydration.invalidate());
65
+ syncClient.on('sync:reconnecting', () => { hydration.invalidate(); });
66
66
  return {
67
67
  modelRegistry,
68
68
  objectPool,
@@ -15,11 +15,12 @@
15
15
  * client assembles the `ablo.<model>` lookup table from these.
16
16
  */
17
17
  import { autorun } from 'mobx';
18
- import { AbloClaimedError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../errors.js';
18
+ import { AbloClaimedError, AbloStaleContextError, AbloValidationError, formatClaimedErrorMessage, toAbloError, } from '../errors.js';
19
19
  import { descriptionFromMeta } from '../coordination/schema.js';
20
20
  import { reconcileFunctionalUpdate, } from './functionalUpdate.js';
21
21
  import { Model, modelAsRow } from '../Model.js';
22
22
  import { toMs } from '../utils/duration.js';
23
+ import { LEASE_TTL_MS } from '../wire/protocol.js';
23
24
  import { assertWriteOptions } from './writeOptionsSchema.js';
24
25
  import { ModelScope } from '../types/index.js';
25
26
  const modelClientMeta = new WeakMap();
@@ -85,9 +86,11 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
85
86
  // TTL (a genuine epoch-ms expiry, not a fabricated watermark), defaulting to
86
87
  // the server's keepalive lease window when no TTL was requested.
87
88
  const activeClaims = new Map();
88
- // Server keepalive lease window (Hub `LEASE_RENEW_TTL_MS`). The fallback
89
- // expiry estimate when a claim is taken without an explicit TTL.
90
- const DEFAULT_LEASE_TTL_MS = 90_000;
89
+ // Server keepalive lease window (Hub `LEASE_RENEW_TTL_MS` the same
90
+ // `LEASE_TTL_MS` from wire/protocol.ts, so client estimate and server
91
+ // lease can't drift). The fallback expiry estimate when a claim is
92
+ // taken without an explicit TTL.
93
+ const DEFAULT_LEASE_TTL_MS = LEASE_TTL_MS;
91
94
  const isClaimHandle = (value) => typeof value === 'object' &&
92
95
  value !== null &&
93
96
  value.object === 'claim' &&
@@ -338,8 +341,9 @@ export function createModelProxy(schemaKey, registeredModelName, objectPool, syn
338
341
  if (options?.filter) {
339
342
  result = result.filter(options.filter);
340
343
  }
341
- if (options?.orderBy) {
342
- const [field, dir] = Object.entries(options.orderBy)[0];
344
+ const orderEntry = options?.orderBy ? Object.entries(options.orderBy)[0] : undefined;
345
+ if (orderEntry) {
346
+ const [field, dir] = orderEntry;
343
347
  result = [...result].sort((a, b) => {
344
348
  const av = a[field];
345
349
  const bv = b[field];
@@ -0,0 +1,63 @@
1
+ /**
2
+ * credentialEndpoint — the endpoint-string shape of `apiKey`.
3
+ *
4
+ * `Ablo({ schema, apiKey: '/api/ablo-session' })` — point the client at your
5
+ * session-mint route and the SDK owns the exchange: it POSTs the endpoint,
6
+ * parses the minted token, keeps it fresh (the credential lifecycle), and
7
+ * classifies failures onto the resolver tri-state. This is the Ably `authUrl`
8
+ * / Liveblocks `authEndpoint` model — the string form is the 95% case; the
9
+ * function form of `apiKey` remains the escape hatch for custom headers,
10
+ * bodies, or non-HTTP mints.
11
+ *
12
+ * Detection is by prefix: a string starting with `/`, `http://`, or
13
+ * `https://` is an endpoint; anything else is a literal key. Real Ablo keys
14
+ * are `sk_`/`pk_`/`ek_`/`rk_`-prefixed, so the two shapes cannot collide.
15
+ * (`ABLO_API_KEY` env values are NEVER endpoint-detected — the env var is
16
+ * always a literal key; see `resolveApiKey`.)
17
+ *
18
+ * Wire contract — matches the `ablo init` scaffold route exactly:
19
+ * POST <endpoint> (same-origin, `credentials: 'include'` so cookies flow)
20
+ * → 200 `{ token, expiresAt? }` fresh short-lived `ek_`/`rk_`
21
+ * → 200 `{ token: null }` or 401/403 the login itself is gone (sign out)
22
+ * → anything else transient — retry, NEVER sign out
23
+ *
24
+ * The tri-state mapping is the whole point of building this in: hand-written
25
+ * thunks routinely get it wrong (mapping any `!res.ok` to `null` signs the
26
+ * user out on a 500). Encoded here once, every consumer inherits the correct
27
+ * terminal-vs-transient split (the Liveblocks `{ error: "forbidden" }` vs
28
+ * retry contract, translated to HTTP statuses).
29
+ */
30
+ /**
31
+ * Async callable that resolves the current credential. Mirrors the shape
32
+ * Anthropic / OpenAI / Stripe ship — used for credential rotation
33
+ * (e.g. AWS STS, GCP IAM, Vault) AND the short-lived per-user browser
34
+ * path (mint a fresh `ek_`/`rk_` from the signed-in session). Re-exported
35
+ * from `./auth` (and thence `./Ablo`) so existing import paths work; defined
36
+ * in this leaf so the credential resolver it types has no import cycle.
37
+ *
38
+ * Contract: resolve a token; resolve `null` when the login itself is gone
39
+ * (terminal → the credential lifecycle treats this as `session_expired` and
40
+ * signs out); or THROW on a transient failure (→ back off and retry, never
41
+ * sign out). A long-lived static `apiKey` string needs none of this — it is
42
+ * used as-is. This is the single credential resolver the SDK supports.
43
+ */
44
+ export type ApiKeySetter = () => Promise<string | null>;
45
+ /**
46
+ * Is this `apiKey` string a session-mint endpoint rather than a literal key?
47
+ * Prefix rule: `/relative/path`, `http://…`, or `https://…`.
48
+ */
49
+ export declare function isCredentialEndpoint(value: string): boolean;
50
+ /**
51
+ * Build the resolver behind an endpoint-string `apiKey`. Conforms to the
52
+ * `ApiKeySetter` contract end-to-end:
53
+ * - resolves the minted token string on success,
54
+ * - resolves `null` when the login is gone (401/403, or an explicit
55
+ * `{ token: null }`) → terminal, the client signs out,
56
+ * - THROWS on anything transient (network failure, 5xx/429, malformed
57
+ * response) → the lifecycle backs off and retries, never signs out.
58
+ *
59
+ * A relative endpoint invoked server-side (Node fetch has no origin) throws —
60
+ * transient by contract, and `credentialLifecycle` already translates that
61
+ * exact failure into an actionable "use an absolute URL server-side" warning.
62
+ */
63
+ export declare function createEndpointCredentialResolver(endpoint: string): ApiKeySetter;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * credentialEndpoint — the endpoint-string shape of `apiKey`.
3
+ *
4
+ * `Ablo({ schema, apiKey: '/api/ablo-session' })` — point the client at your
5
+ * session-mint route and the SDK owns the exchange: it POSTs the endpoint,
6
+ * parses the minted token, keeps it fresh (the credential lifecycle), and
7
+ * classifies failures onto the resolver tri-state. This is the Ably `authUrl`
8
+ * / Liveblocks `authEndpoint` model — the string form is the 95% case; the
9
+ * function form of `apiKey` remains the escape hatch for custom headers,
10
+ * bodies, or non-HTTP mints.
11
+ *
12
+ * Detection is by prefix: a string starting with `/`, `http://`, or
13
+ * `https://` is an endpoint; anything else is a literal key. Real Ablo keys
14
+ * are `sk_`/`pk_`/`ek_`/`rk_`-prefixed, so the two shapes cannot collide.
15
+ * (`ABLO_API_KEY` env values are NEVER endpoint-detected — the env var is
16
+ * always a literal key; see `resolveApiKey`.)
17
+ *
18
+ * Wire contract — matches the `ablo init` scaffold route exactly:
19
+ * POST <endpoint> (same-origin, `credentials: 'include'` so cookies flow)
20
+ * → 200 `{ token, expiresAt? }` fresh short-lived `ek_`/`rk_`
21
+ * → 200 `{ token: null }` or 401/403 the login itself is gone (sign out)
22
+ * → anything else transient — retry, NEVER sign out
23
+ *
24
+ * The tri-state mapping is the whole point of building this in: hand-written
25
+ * thunks routinely get it wrong (mapping any `!res.ok` to `null` signs the
26
+ * user out on a 500). Encoded here once, every consumer inherits the correct
27
+ * terminal-vs-transient split (the Liveblocks `{ error: "forbidden" }` vs
28
+ * retry contract, translated to HTTP statuses).
29
+ */
30
+ /**
31
+ * Is this `apiKey` string a session-mint endpoint rather than a literal key?
32
+ * Prefix rule: `/relative/path`, `http://…`, or `https://…`.
33
+ */
34
+ export function isCredentialEndpoint(value) {
35
+ return value.startsWith('/') || /^https?:\/\//i.test(value);
36
+ }
37
+ /**
38
+ * Build the resolver behind an endpoint-string `apiKey`. Conforms to the
39
+ * `ApiKeySetter` contract end-to-end:
40
+ * - resolves the minted token string on success,
41
+ * - resolves `null` when the login is gone (401/403, or an explicit
42
+ * `{ token: null }`) → terminal, the client signs out,
43
+ * - THROWS on anything transient (network failure, 5xx/429, malformed
44
+ * response) → the lifecycle backs off and retries, never signs out.
45
+ *
46
+ * A relative endpoint invoked server-side (Node fetch has no origin) throws —
47
+ * transient by contract, and `credentialLifecycle` already translates that
48
+ * exact failure into an actionable "use an absolute URL server-side" warning.
49
+ */
50
+ export function createEndpointCredentialResolver(endpoint) {
51
+ return async () => {
52
+ // fetch() rejections (offline, DNS, relative URL in Node) propagate as-is:
53
+ // a throw IS the transient signal in the resolver contract.
54
+ const res = await fetch(endpoint, {
55
+ method: 'POST',
56
+ credentials: 'include',
57
+ });
58
+ // The login itself is gone — terminal. Only these two statuses sign out.
59
+ if (res.status === 401 || res.status === 403)
60
+ return null;
61
+ // 5xx / 429 / anything unexpected: the login may be perfectly valid —
62
+ // transient, retry later.
63
+ if (!res.ok) {
64
+ throw new Error(`credential endpoint ${endpoint} answered ${res.status} — transient, will retry`);
65
+ }
66
+ let body;
67
+ try {
68
+ body = await res.json();
69
+ }
70
+ catch {
71
+ throw new Error(`credential endpoint ${endpoint} returned non-JSON — expected { token }`);
72
+ }
73
+ // Distinguish "explicitly signed out" ({ token: null }) from "not a mint
74
+ // endpoint at all" (no `token` key — misconfiguration must be LOUD and
75
+ // transient, never a silent sign-out).
76
+ if (typeof body !== 'object' || body === null || !('token' in body)) {
77
+ throw new Error(`credential endpoint ${endpoint} returned no \`token\` field — expected { token }`);
78
+ }
79
+ const token = body.token;
80
+ if (token === null || token === undefined)
81
+ return null;
82
+ if (typeof token !== 'string') {
83
+ throw new Error(`credential endpoint ${endpoint} returned a non-string \`token\``);
84
+ }
85
+ return token;
86
+ };
87
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The canonical Ablo Cloud endpoint constants — a ZERO-dependency leaf.
3
+ *
4
+ * This is the single place the hosted API host is declared. Everything else
5
+ * (`client/auth.ts` URL resolution, the CLI's `DEFAULT_URL`, the Data Source
6
+ * connector's dial-out base, the generated OpenAPI `servers` entry, the
7
+ * network probe's default) imports from here, so the next API-domain
8
+ * migration is ONE edit — the `LEGACY_HOSTED_API_HOSTS` rewrite list in
9
+ * `client/auth.ts` (four retired ablo.finance hosts) is proof such
10
+ * migrations happen.
11
+ *
12
+ * Kept dependency-free on purpose: `schema/openapi.ts` and `source/connector.ts`
13
+ * consume it, and routing them through `client/auth.ts` would drag the error
14
+ * registry + credential policy (and a `schema → client → errors →
15
+ * coordination → schema` cycle) into subpaths that are otherwise clean.
16
+ */
17
+ /** The hosted API domain (no scheme). */
18
+ export declare const ABLO_HOSTED_API_DOMAIN = "api.abloatai.com";
19
+ /** The hosted HTTP origin — `https://` + {@link ABLO_HOSTED_API_DOMAIN}. */
20
+ export declare const ABLO_HOSTED_HTTP_BASE_URL = "https://api.abloatai.com";
21
+ /** Default `baseURL` when the caller passes none. Same value as
22
+ * {@link ABLO_HOSTED_HTTP_BASE_URL}; kept as a distinct name because it is
23
+ * the documented client-options default. */
24
+ export declare const ABLO_DEFAULT_BASE_URL = "https://api.abloatai.com";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The canonical Ablo Cloud endpoint constants — a ZERO-dependency leaf.
3
+ *
4
+ * This is the single place the hosted API host is declared. Everything else
5
+ * (`client/auth.ts` URL resolution, the CLI's `DEFAULT_URL`, the Data Source
6
+ * connector's dial-out base, the generated OpenAPI `servers` entry, the
7
+ * network probe's default) imports from here, so the next API-domain
8
+ * migration is ONE edit — the `LEGACY_HOSTED_API_HOSTS` rewrite list in
9
+ * `client/auth.ts` (four retired ablo.finance hosts) is proof such
10
+ * migrations happen.
11
+ *
12
+ * Kept dependency-free on purpose: `schema/openapi.ts` and `source/connector.ts`
13
+ * consume it, and routing them through `client/auth.ts` would drag the error
14
+ * registry + credential policy (and a `schema → client → errors →
15
+ * coordination → schema` cycle) into subpaths that are otherwise clean.
16
+ */
17
+ /** The hosted API domain (no scheme). */
18
+ export const ABLO_HOSTED_API_DOMAIN = 'api.abloatai.com';
19
+ /** The hosted HTTP origin — `https://` + {@link ABLO_HOSTED_API_DOMAIN}. */
20
+ export const ABLO_HOSTED_HTTP_BASE_URL = `https://${ABLO_HOSTED_API_DOMAIN}`;
21
+ /** Default `baseURL` when the caller passes none. Same value as
22
+ * {@link ABLO_HOSTED_HTTP_BASE_URL}; kept as a distinct name because it is
23
+ * the documented client-options default. */
24
+ export const ABLO_DEFAULT_BASE_URL = ABLO_HOSTED_HTTP_BASE_URL;
@@ -23,7 +23,7 @@
23
23
  * surface as the browser client — typed proxies, stateless transport.
24
24
  */
25
25
  import { type AbloApiClientOptions } from './ApiClient.js';
26
- import type { CommitReceipt, CommitResource, HttpClaimApi, ModelRead, ModelReadOptions, CreateSessionParams, AbloSession } from './Ablo.js';
26
+ import type { CommitReceipt, CommitResource, HttpClaimApi, ModelRead, ModelReadOptions, CreateSessionParams, AbloSession } from './resourceTypes.js';
27
27
  import type { ModelCreateParams, ModelDeleteParams, ServerReadOptions, ModelRetrieveParams, ModelUpdateParams } from './createModelProxy.js';
28
28
  import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
29
29
  import type { ModelUpdater, ContentionOptions } from './functionalUpdate.js';
@@ -168,9 +168,10 @@ async function resolveHosted(input) {
168
168
  const baseUrl = input.baseUrl;
169
169
  // The refresh path re-runs `exchangeApiKey` with a freshly-resolved apiKey, so
170
170
  // it needs the same argument bag the policy used for the initial exchange.
171
+ const participantKind = input.kind === 'agent' ? 'agent' : 'system';
171
172
  const exchangeArgs = {
172
173
  baseUrl,
173
- participantKind: (input.kind === 'agent' ? 'agent' : 'system'),
174
+ participantKind,
174
175
  participantId: input.options.agentId ?? input.options.user?.id,
175
176
  wideScope: true,
176
177
  ttlSeconds: 3600,