@abloatai/ablo 0.11.1 → 0.12.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 +49 -0
- package/README.md +10 -2
- package/dist/Model.d.ts +39 -0
- package/dist/Model.js +68 -0
- package/dist/ai-sdk/claim-broadcast.d.ts +4 -3
- package/dist/ai-sdk/claim-broadcast.js +2 -2
- package/dist/ai-sdk/wrap.d.ts +5 -4
- package/dist/ai-sdk/wrap.js +3 -3
- package/dist/auth/credentialPolicy.d.ts +145 -0
- package/dist/auth/credentialPolicy.js +130 -0
- package/dist/cli.cjs +42 -7
- package/dist/client/Ablo.d.ts +64 -91
- package/dist/client/Ablo.js +43 -103
- package/dist/client/ApiClient.d.ts +10 -1
- package/dist/client/ApiClient.js +45 -22
- package/dist/client/auth.d.ts +12 -5
- package/dist/client/auth.js +2 -1
- package/dist/client/createModelProxy.d.ts +64 -17
- package/dist/client/createModelProxy.js +18 -12
- package/dist/client/httpClient.d.ts +17 -3
- package/dist/client/httpClient.js +1 -0
- package/dist/client/identity.js +134 -122
- package/dist/client/index.d.ts +1 -1
- package/dist/client/sessionMint.d.ts +15 -0
- package/dist/client/sessionMint.js +86 -0
- package/dist/coordination/schema.d.ts +1 -1
- package/dist/coordination/schema.js +3 -1
- package/dist/errorCodes.d.ts +2 -0
- package/dist/errorCodes.js +2 -0
- package/dist/errors.d.ts +6 -3
- package/dist/errors.js +9 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -7
- package/dist/mutators/RecordingTransaction.js +14 -42
- package/dist/react/AbloProvider.d.ts +12 -13
- package/dist/react/AbloProvider.js +10 -10
- package/dist/react/context.d.ts +10 -45
- package/dist/react/context.js +12 -17
- package/dist/react/index.d.ts +8 -10
- package/dist/react/index.js +8 -11
- package/dist/react/useMutators.js +3 -2
- package/dist/react/useSyncStatus.d.ts +1 -1
- package/dist/react/useUndoScope.js +3 -2
- package/dist/realtime/index.d.ts +1 -1
- package/dist/schema/generate.js +1 -2
- package/dist/schema/model.d.ts +10 -3
- package/dist/schema/schema.d.ts +13 -2
- package/dist/schema/schema.js +26 -0
- package/dist/surface.d.ts +29 -0
- package/dist/surface.js +60 -0
- package/dist/sync/ConnectionManager.d.ts +16 -5
- package/dist/sync/ConnectionManager.js +42 -7
- package/dist/sync/createClaimStream.js +5 -4
- package/dist/sync/participants.js +1 -1
- package/dist/transactions/TransactionQueue.d.ts +0 -11
- package/dist/transactions/TransactionQueue.js +12 -56
- package/dist/types/global.d.ts +3 -0
- package/dist/types/streams.d.ts +17 -29
- package/dist/utils/mobx-setup.js +1 -0
- package/docs/api-keys.md +49 -0
- package/docs/api.md +3 -2
- package/docs/client-behavior.md +1 -0
- package/docs/coordination.md +75 -21
- package/docs/examples/existing-python-backend.md +9 -5
- package/docs/examples/scoped-agent.md +1 -1
- package/docs/guarantees.md +4 -3
- package/docs/identity.md +89 -82
- package/docs/integration-guide.md +19 -10
- package/docs/migration.md +11 -3
- package/docs/quickstart.md +6 -2
- package/docs/react.md +3 -3
- package/docs/schema-contract.md +23 -5
- package/llms-full.txt +18 -16
- package/llms.txt +6 -6
- package/package.json +1 -1
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.js +0 -9
- package/dist/principal.d.ts +0 -44
- package/dist/principal.js +0 -49
- package/dist/react/SyncGroupProvider.d.ts +0 -19
- package/dist/react/SyncGroupProvider.js +0 -44
- package/dist/react/useClaim.d.ts +0 -29
- package/dist/react/useClaim.js +0 -42
- package/dist/react/usePresence.d.ts +0 -32
- package/dist/react/usePresence.js +0 -41
package/dist/cli.cjs
CHANGED
|
@@ -276804,6 +276804,7 @@ var ERROR_CODES = {
|
|
|
276804
276804
|
model_claimed: wire("claim", 409, false, "The model instance is claimed by another participant."),
|
|
276805
276805
|
model_claimed_timeout: wire("claim", 409, false, "Timed out waiting for a model claim to clear."),
|
|
276806
276806
|
model_claim_not_configured: client("claim", "Claiming requires the collaboration runtime, which the standard Ablo({ schema, apiKey }) client wires up for every model automatically \u2014 there is no per-model claim configuration to add. This appears only when a model proxy is constructed directly without that runtime (an internal/advanced path)."),
|
|
276807
|
+
model_watch_not_configured: client("claim", "watch() opens a presence/claim subscription and needs a live WebSocket, so it is unavailable on the HTTP transport and on model proxies built without a socket. Use the standard Ablo({ schema, apiKey }) client (default WebSocket transport)."),
|
|
276807
276808
|
// ── stale context / idempotency (409) ──────────────────────────────
|
|
276808
276809
|
stale_context: wire("conflict", 409, true, "The write carried a readAt watermark that is now stale; re-read and retry."),
|
|
276809
276810
|
idempotency_conflict: wire("conflict", 409, false, "The same Idempotency-Key was reused with a different request body."),
|
|
@@ -276855,6 +276856,7 @@ var ERROR_CODES = {
|
|
|
276855
276856
|
schema_scope_kind_invalid: wire("schema", 400, false, "A scope kind in the schema is invalid."),
|
|
276856
276857
|
schema_field_not_camelcase: wire("schema", 400, false, "A schema field name is not camelCase."),
|
|
276857
276858
|
schema_field_consecutive_caps: wire("schema", 400, false, "A schema field name has consecutive capital letters."),
|
|
276859
|
+
schema_reserved_field: client("schema", "A model redeclared a reserved base field (id, createdAt, updatedAt, organizationId, createdBy) that the SDK provides automatically."),
|
|
276858
276860
|
schema_grants_shape_invalid: wire("schema", 400, false, "A grants declaration has an invalid shape."),
|
|
276859
276861
|
schema_grants_identifier_unsafe: wire("schema", 400, false, "A grants declaration referenced an unsafe identifier."),
|
|
276860
276862
|
schema_grants_relation_kind: wire("schema", 400, false, "A grants relation referenced an invalid kind."),
|
|
@@ -277119,7 +277121,9 @@ var modelClaimSchema = import_zod3.z.object({
|
|
|
277119
277121
|
id: import_zod3.z.string(),
|
|
277120
277122
|
actor: import_zod3.z.string(),
|
|
277121
277123
|
participantKind: wireParticipantKindSchema,
|
|
277122
|
-
|
|
277124
|
+
/** Human-readable phase (`'editing'`). The public SDK field; the WS/HTTP
|
|
277125
|
+
* wire carries the same value as `action` (healed on read). */
|
|
277126
|
+
reason: import_zod3.z.string(),
|
|
277123
277127
|
description: import_zod3.z.string().optional(),
|
|
277124
277128
|
field: import_zod3.z.string().optional(),
|
|
277125
277129
|
status: import_zod3.z.enum(["active", "queued"]).optional(),
|
|
@@ -279616,6 +279620,23 @@ var import_source = require("@abloatai/ablo/source");
|
|
|
279616
279620
|
// src/cli/push.ts
|
|
279617
279621
|
init_cjs_shims();
|
|
279618
279622
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
279623
|
+
|
|
279624
|
+
// src/auth/credentialPolicy.ts
|
|
279625
|
+
init_cjs_shims();
|
|
279626
|
+
var KIND_BY_PREFIX = [
|
|
279627
|
+
["sk_", "secret"],
|
|
279628
|
+
["ek_", "ephemeral"],
|
|
279629
|
+
["rk_", "restricted"],
|
|
279630
|
+
["pk_", "publishable"]
|
|
279631
|
+
];
|
|
279632
|
+
function classifyCredentialKind(value) {
|
|
279633
|
+
for (const [prefix, kind] of KIND_BY_PREFIX) {
|
|
279634
|
+
if (value.startsWith(prefix)) return kind;
|
|
279635
|
+
}
|
|
279636
|
+
return null;
|
|
279637
|
+
}
|
|
279638
|
+
|
|
279639
|
+
// src/cli/push.ts
|
|
279619
279640
|
var import_fs4 = require("fs");
|
|
279620
279641
|
var import_path3 = require("path");
|
|
279621
279642
|
var import_schema2 = require("@abloatai/ablo/schema");
|
|
@@ -279928,7 +279949,7 @@ async function push(argv) {
|
|
|
279928
279949
|
console.error(import_picocolors3.default.dim(` Re-push with ${import_picocolors3.default.bold("--force")} to override, or use ${import_picocolors3.default.bold("--rename old:new")} if you renamed a model.`));
|
|
279929
279950
|
} else if (status2 === 403) {
|
|
279930
279951
|
console.error(import_picocolors3.default.red(` Forbidden: ${body.message ?? body.reason ?? "key lacks schema:push scope"}`));
|
|
279931
|
-
if (args.apiKey
|
|
279952
|
+
if (args.apiKey != null && classifyCredentialKind(args.apiKey) === "restricted") {
|
|
279932
279953
|
console.error(
|
|
279933
279954
|
import_picocolors3.default.dim(
|
|
279934
279955
|
` Schema pushes need a SECRET key: ${import_picocolors3.default.bold("sk_test_")} (sandbox dev loop) or a dashboard ${import_picocolors3.default.bold("sk_live_")} (production deploy: ${import_picocolors3.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")}).`
|
|
@@ -280223,7 +280244,7 @@ function classifyKey(apiKey, activeMode) {
|
|
|
280223
280244
|
reason: `Production schema deploys run one-shot: ${import_picocolors6.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")} (or ${import_picocolors6.default.bold("ablo mode production")}). ${import_picocolors6.default.bold("--watch")} is sandbox-only.`
|
|
280224
280245
|
};
|
|
280225
280246
|
}
|
|
280226
|
-
if (apiKey
|
|
280247
|
+
if (classifyCredentialKind(apiKey) === "restricted") {
|
|
280227
280248
|
return {
|
|
280228
280249
|
ok: false,
|
|
280229
280250
|
reason: `Restricted (${import_picocolors6.default.bold("rk_")}) keys can't push schema. Use a secret key: ${import_picocolors6.default.bold("sk_test_")} for the sandbox dev loop, or ${import_picocolors6.default.bold("sk_live_")} with ${import_picocolors6.default.bold("npx ablo push")} for a production deploy.`
|
|
@@ -281052,7 +281073,7 @@ function requireKey2(mode2) {
|
|
|
281052
281073
|
);
|
|
281053
281074
|
process.exit(1);
|
|
281054
281075
|
}
|
|
281055
|
-
if (
|
|
281076
|
+
if (classifyCredentialKind(apiKey) !== "secret") {
|
|
281056
281077
|
console.error(import_picocolors12.default.red(" Managing webhooks requires a secret key ") + import_picocolors12.default.dim("(sk_test_ / sk_live_)."));
|
|
281057
281078
|
process.exit(1);
|
|
281058
281079
|
}
|
|
@@ -282922,9 +282943,23 @@ import Ablo from '@abloatai/ablo';
|
|
|
282922
282943
|
import { AbloProvider } from '@abloatai/ablo/react';
|
|
282923
282944
|
import { schema } from '@/ablo/schema';
|
|
282924
282945
|
|
|
282925
|
-
// The browser client holds NO secret. \`
|
|
282926
|
-
// which mints a short-lived session token (already scoped to the org +
|
|
282927
|
-
|
|
282946
|
+
// The browser client holds NO secret. The \`apiKey\` resolver fetches the route
|
|
282947
|
+
// below, which mints a short-lived session token (already scoped to the org +
|
|
282948
|
+
// user); the client keeps it fresh (refresh timer + wake/online/focus re-mint).
|
|
282949
|
+
// Contract: return the token, return \`null\` when the user is signed out
|
|
282950
|
+
// (\u2192 the client signs out), or throw on a transient failure (\u2192 it retries).
|
|
282951
|
+
const ablo = Ablo({
|
|
282952
|
+
schema,
|
|
282953
|
+
apiKey: async () => {
|
|
282954
|
+
const res = await fetch('/api/ablo-session', {
|
|
282955
|
+
method: 'POST',
|
|
282956
|
+
credentials: 'include',
|
|
282957
|
+
});
|
|
282958
|
+
if (!res.ok) return null;
|
|
282959
|
+
const { token } = (await res.json()) as { token: string | null };
|
|
282960
|
+
return token;
|
|
282961
|
+
},
|
|
282962
|
+
});
|
|
282928
282963
|
|
|
282929
282964
|
export function Providers({ children }: { children: React.ReactNode }) {
|
|
282930
282965
|
return <AbloProvider client={ablo}>{children}</AbloProvider>;
|
package/dist/client/Ablo.d.ts
CHANGED
|
@@ -28,7 +28,6 @@ import type { SyncWebSocket } from '../sync/SyncWebSocket.js';
|
|
|
28
28
|
import type { SyncGroupInput } from '../schema/roles.js';
|
|
29
29
|
import { type SyncStatus } from '../BaseSyncedStore.js';
|
|
30
30
|
import type { ClaimStream, ClaimWaitOptions, PresenceStream, Snapshot } from '../types/streams.js';
|
|
31
|
-
import type { ParticipantManager } from '../sync/participants.js';
|
|
32
31
|
import type { ClaimHandle, Duration, Claim } from '../types/streams.js';
|
|
33
32
|
import { type AbloApi, type AbloApiClientOptions, type AbloApiClaims } from './ApiClient.js';
|
|
34
33
|
import { type AbloHttpClient, type AbloHttpClientOptions } from './httpClient.js';
|
|
@@ -77,35 +76,24 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
77
76
|
* usually pass nothing). A long-lived key needs no refresh; the client uses
|
|
78
77
|
* it as-is.
|
|
79
78
|
*
|
|
80
|
-
* Accepts a static string
|
|
81
|
-
*
|
|
79
|
+
* Accepts a static string OR an async `() => Promise<string | null>` resolver
|
|
80
|
+
* — the single credential path. Use the resolver form for two cases:
|
|
82
81
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
*
|
|
91
|
-
* fresh bearer (`ek_`/`rk_`) your backend minted for the signed-in user. The
|
|
92
|
-
* client calls it once before connect and then keeps the key fresh for you —
|
|
93
|
-
* a refresh timer ahead of expiry plus re-mint on OS-wake / network-online /
|
|
94
|
-
* tab-focus, and a reactive re-mint when a probe finds the key stale. You
|
|
95
|
-
* never call a refresh method (Supabase `autoRefreshToken` model).
|
|
82
|
+
* - **Key rotation** (server): pull a fresh `sk_`/`pk_` from a vault on each
|
|
83
|
+
* bootstrap (AWS STS, GCP IAM, Vault).
|
|
84
|
+
* - **Short-lived per-user browser** auth: return the fresh `ek_`/`rk_` bearer
|
|
85
|
+
* your backend minted for the signed-in user. The client mints once before
|
|
86
|
+
* connect, then keeps it fresh for you — a refresh timer ahead of expiry
|
|
87
|
+
* plus re-mint on OS-wake / network-online / tab-focus, and a reactive
|
|
88
|
+
* re-mint when a probe finds the key stale. You never call a refresh method
|
|
89
|
+
* (Supabase `autoRefreshToken` model).
|
|
96
90
|
*
|
|
97
|
-
*
|
|
98
|
-
* (terminal →
|
|
99
|
-
*
|
|
91
|
+
* Resolver contract: resolve a token; resolve `null` when the login itself is
|
|
92
|
+
* gone (terminal → the client signs out / fails `ready()` with `session_expired`);
|
|
93
|
+
* or THROW on a transient failure (→ back off and retry, never sign out). A
|
|
94
|
+
* static string never refreshes — it is used as-is.
|
|
100
95
|
*/
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Convenience over {@link getToken}: a URL on YOUR backend that returns
|
|
104
|
-
* `{ token }`. The client POSTs to it (with cookies, so it's authed by the
|
|
105
|
-
* user's session) to mint + refresh the bearer. Ignored when `getToken` is
|
|
106
|
-
* set. Pure sugar — `getToken: () => fetch(url).then(r => r.json()).then(b => b.token)`.
|
|
107
|
-
*/
|
|
108
|
-
authEndpoint?: string | undefined;
|
|
96
|
+
apiKey?: string | ApiKeySetter | null | undefined;
|
|
109
97
|
/**
|
|
110
98
|
* Direct-URL convenience connector: a connection string to your own Postgres
|
|
111
99
|
* that Ablo can register for a dedicated tenant.
|
|
@@ -138,6 +126,9 @@ export interface AbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
138
126
|
* `AbloHttpClient<S>`, so stateful-only capabilities (`get`/`getAll`,
|
|
139
127
|
* `onChange`) are compile errors rather than latent runtime gaps.
|
|
140
128
|
*
|
|
129
|
+
* Note: session/credential minting (`sessions.create`) currently runs on the
|
|
130
|
+
* stateful (default) client, not the http client.
|
|
131
|
+
*
|
|
141
132
|
* @default 'websocket'
|
|
142
133
|
*/
|
|
143
134
|
transport?: 'websocket' | 'http' | undefined;
|
|
@@ -225,18 +216,6 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
225
216
|
* only for the advanced Model / Claim / Commit client.
|
|
226
217
|
*/
|
|
227
218
|
schema: Schema<S>;
|
|
228
|
-
/**
|
|
229
|
-
* Short-lived-bearer resolver for the per-user browser path (mirrors the
|
|
230
|
-
* public {@link AbloOptions.getToken}). The client mints the first token
|
|
231
|
-
* before connect and refreshes it (timer + wake/online/focus) — see
|
|
232
|
-
* {@link resolveCredentialResolver}.
|
|
233
|
-
*/
|
|
234
|
-
getToken?: (() => Promise<string | null>) | undefined;
|
|
235
|
-
/**
|
|
236
|
-
* Backend URL returning `{ token }`; sugar over {@link getToken}. Mirrors the
|
|
237
|
-
* public {@link AbloOptions.authEndpoint}.
|
|
238
|
-
*/
|
|
239
|
-
authEndpoint?: string | undefined;
|
|
240
219
|
/**
|
|
241
220
|
* @deprecated Server derives participant kind from the apiKey's
|
|
242
221
|
* scope. Pass apiKey only; this option will be removed once the
|
|
@@ -353,8 +332,14 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
353
332
|
*/
|
|
354
333
|
configOverrides?: Partial<SyncEngineConfig>;
|
|
355
334
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
335
|
+
* Sync groups (entity scopes) this client subscribes to. **Provisional, not
|
|
336
|
+
* deprecated** — pick the right lane: normally the server derives these from
|
|
337
|
+
* the apiKey's scope, but passing them is still REQUIRED today in any config
|
|
338
|
+
* where the key doesn't resolve them (omitting yields a `degenerate
|
|
339
|
+
* syncGroups` warning and a zero-fan-out client). Keep passing it explicitly
|
|
340
|
+
* until the server-derived path ships in Phase 3, at which point it becomes a
|
|
341
|
+
* true no-op and is removed. Build values with `syncGroup(kind, id)` from
|
|
342
|
+
* `@abloatai/ablo/schema`.
|
|
358
343
|
*/
|
|
359
344
|
syncGroups?: string[];
|
|
360
345
|
/**
|
|
@@ -385,8 +370,8 @@ export interface InternalAbloOptions<S extends SchemaRecord = SchemaRecord> {
|
|
|
385
370
|
* `create({ data })` / `update({ id, data })` / `delete({ id })` — writes
|
|
386
371
|
* `claim({ id })` — durable claim handle for coordinated writes
|
|
387
372
|
*/
|
|
388
|
-
export type {
|
|
389
|
-
import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams,
|
|
373
|
+
export type { LocalCountOptions, LocalReadOptions, ModelListScope, ServerReadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './createModelProxy.js';
|
|
374
|
+
import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ServerReadOptions } from './createModelProxy.js';
|
|
390
375
|
export type ModelOperationAction = 'create' | 'update' | 'delete' | 'archive' | 'unarchive';
|
|
391
376
|
export type CommitWait = 'queued' | 'confirmed';
|
|
392
377
|
export interface ModelRead<T = Record<string, unknown>> {
|
|
@@ -394,30 +379,24 @@ export interface ModelRead<T = Record<string, unknown>> {
|
|
|
394
379
|
readonly stamp: number;
|
|
395
380
|
readonly claims: readonly ModelClaim[];
|
|
396
381
|
}
|
|
397
|
-
export type IfClaimedPolicy = 'return' | '
|
|
382
|
+
export type IfClaimedPolicy = 'return' | 'fail';
|
|
398
383
|
export interface ClaimedOptions {
|
|
399
384
|
/**
|
|
400
|
-
* What to do when another participant has claimed the target
|
|
401
|
-
* includes active claim metadata in the response
|
|
402
|
-
* claim
|
|
385
|
+
* What to do when another participant has claimed the target: `return`
|
|
386
|
+
* includes active claim metadata in the response; `fail` throws
|
|
387
|
+
* `AbloClaimedError`. Waiting for a claim to clear is a claim-side concern —
|
|
388
|
+
* take `ablo.<model>.claim({ id })` (it queues fairly); reads never block.
|
|
403
389
|
*/
|
|
404
390
|
readonly ifClaimed?: IfClaimedPolicy;
|
|
405
|
-
/** Max time to wait for peer claims to clear, in milliseconds. */
|
|
406
|
-
readonly claimedTimeout?: number;
|
|
407
|
-
/** HTTP API polling interval while waiting. WebSocket clients ignore it. */
|
|
408
|
-
readonly claimedPollInterval?: number;
|
|
409
|
-
/**
|
|
410
|
-
* Backpressure for `ifClaimed: 'wait'`: reject instead of waiting if the
|
|
411
|
-
* row's FIFO line is already `>= maxQueueDepth` deep.
|
|
412
|
-
*/
|
|
413
|
-
readonly maxQueueDepth?: number;
|
|
414
391
|
}
|
|
415
392
|
export type { ClaimWaitOptions } from '../types/streams.js';
|
|
416
393
|
export interface ModelReadOptions extends ClaimedOptions {
|
|
417
394
|
}
|
|
418
395
|
export interface ClaimCreateOptions {
|
|
419
396
|
readonly target: ModelTarget;
|
|
420
|
-
|
|
397
|
+
/** Human-readable phase shown to peers — `'editing'`, `'writing'`. The same
|
|
398
|
+
* word on every claim surface; serialized on the wire as `action`. */
|
|
399
|
+
readonly reason: string;
|
|
421
400
|
readonly ttl?: Duration;
|
|
422
401
|
/**
|
|
423
402
|
* Join the server's fair FIFO queue when the target is already claimed,
|
|
@@ -519,6 +498,20 @@ export interface HttpClaimApi<T> {
|
|
|
519
498
|
reorder(params: ClaimReorderParams<T>): Promise<void>;
|
|
520
499
|
}
|
|
521
500
|
export interface ModelClient<T = Record<string, unknown>> {
|
|
501
|
+
/**
|
|
502
|
+
* Single-row read over HTTP. **Returns an envelope, not the bare row** — the
|
|
503
|
+
* row is on `.data`, alongside the `.stamp` watermark (for stale-context
|
|
504
|
+
* guards on the following write) and any active `.claims`. A stateless HTTP
|
|
505
|
+
* client can't synthesize the watermark from a local snapshot, so the
|
|
506
|
+
* envelope is load-bearing here (the WebSocket client's `retrieve` returns
|
|
507
|
+
* `T | undefined` because it reads from the hydrated pool).
|
|
508
|
+
*
|
|
509
|
+
* ```ts
|
|
510
|
+
* const deal = await ablo.deals.retrieve({ id });
|
|
511
|
+
* deal.data?.recommendation; // ← the row is on .data
|
|
512
|
+
* deal.stamp; // watermark — pass to the next write's readAt
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
522
515
|
retrieve(params: ModelReadOptions & {
|
|
523
516
|
readonly id: string;
|
|
524
517
|
}): Promise<ModelRead<T>>;
|
|
@@ -527,7 +520,7 @@ export interface ModelClient<T = Record<string, unknown>> {
|
|
|
527
520
|
* `limit`. Present on the stateless protocol client; the store-backed
|
|
528
521
|
* `.model(name)` accessor omits it (use the typed `ablo.<model>.list` there).
|
|
529
522
|
*/
|
|
530
|
-
list?(options?:
|
|
523
|
+
list?(options?: ServerReadOptions<T>): Promise<T[]>;
|
|
531
524
|
create(params: ModelMutationOptions & {
|
|
532
525
|
readonly data: Record<string, unknown>;
|
|
533
526
|
readonly id?: string | null;
|
|
@@ -560,7 +553,7 @@ export interface CreateUserSessionParams {
|
|
|
560
553
|
id: string;
|
|
561
554
|
};
|
|
562
555
|
/** Sync groups this session may subscribe to — typed (`'default'` or
|
|
563
|
-
* `<namespace>:<id>`; build with `syncGroup
|
|
556
|
+
* `<namespace>:<id>`; build with `syncGroup(kind, id)` from
|
|
564
557
|
* `@abloatai/ablo/schema`). Omit for the server default:
|
|
565
558
|
* `[org:<your org>, user:<user.id>]`. */
|
|
566
559
|
syncGroups?: readonly SyncGroupInput[];
|
|
@@ -585,7 +578,7 @@ export interface CreateAgentSessionParams<S extends SchemaRecord> {
|
|
|
585
578
|
[M in keyof S & string]?: readonly SessionOperation[];
|
|
586
579
|
};
|
|
587
580
|
/** Sync groups this session may subscribe to — typed (`'default'` or
|
|
588
|
-
* `<namespace>:<id>`; build with `syncGroup
|
|
581
|
+
* `<namespace>:<id>`; build with `syncGroup(kind, id)` from
|
|
589
582
|
* `@abloatai/ablo/schema`). Omit for the server default: the org
|
|
590
583
|
* anchor (`org:<your org>`) + the agent's own anchor. */
|
|
591
584
|
syncGroups?: readonly SyncGroupInput[];
|
|
@@ -667,7 +660,7 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
667
660
|
* Replace the bearer auth token used for the WebSocket upgrade and HTTP
|
|
668
661
|
* requests, WITHOUT tearing down the engine. Use to push a refreshed
|
|
669
662
|
* short-lived access key (the Stripe-style `ek_`/`rk_`) before it expires —
|
|
670
|
-
*
|
|
663
|
+
* the client's `apiKey`-resolver refresh loop calls this. Reuses the same
|
|
671
664
|
* rotation path as the internal capability-token refresh; safe to call before
|
|
672
665
|
* `ready()`. Also nudges a parked connection to re-probe with the new token.
|
|
673
666
|
*/
|
|
@@ -675,8 +668,8 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
675
668
|
/**
|
|
676
669
|
* Resolve the active bearer credential this engine authenticates with — the
|
|
677
670
|
* live `ek_`/`rk_` the WebSocket and HTTP transports currently carry (kept
|
|
678
|
-
* fresh by the `
|
|
679
|
-
* key. Returns `null` when no credential is set yet. Use it to authenticate
|
|
671
|
+
* fresh by the `apiKey`-resolver refresh loop), falling back to a configured
|
|
672
|
+
* API key. Returns `null` when no credential is set yet. Use it to authenticate
|
|
680
673
|
* a side-band request to the same server with the very token this client
|
|
681
674
|
* already holds — no extra mint round-trip.
|
|
682
675
|
*/
|
|
@@ -685,10 +678,10 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
685
678
|
* Register a re-mint hook for the short-lived access key. The connection
|
|
686
679
|
* layer calls it WHEN it finds the key stale (a `credential_stale` probe) or
|
|
687
680
|
* on an external nudge; the hook mints a fresh `ek_`/`rk_` from the still-valid
|
|
688
|
-
* login. Mirrors the `
|
|
689
|
-
* the login itself is gone (→ sign out), or THROW on a transient
|
|
690
|
-
* back off, never sign out).
|
|
691
|
-
* `
|
|
681
|
+
* login. Mirrors the `apiKey`-resolver contract: resolve a token, resolve
|
|
682
|
+
* `null` when the login itself is gone (→ sign out), or THROW on a transient
|
|
683
|
+
* failure (→ back off, never sign out). The client wires this automatically
|
|
684
|
+
* from a function `apiKey`. Safe to call before `ready()`.
|
|
692
685
|
*/
|
|
693
686
|
setCredentialRefresher(refresher: (() => Promise<string | null>) | null): void;
|
|
694
687
|
/**
|
|
@@ -703,14 +696,15 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
703
696
|
* Mint a short-lived, scoped **session token** for one end user — the
|
|
704
697
|
* Stripe `ephemeralKeys.create` / Supabase session shape. Call this on YOUR
|
|
705
698
|
* BACKEND (where the `sk_` secret key lives), then hand the returned
|
|
706
|
-
* `token` to that user's browser (typically via
|
|
707
|
-
* fetches). The browser presents it as the bearer; the sync-server verifies
|
|
699
|
+
* `token` to that user's browser (typically via a token route the browser's
|
|
700
|
+
* `apiKey` resolver fetches). The browser presents it as the bearer; the sync-server verifies
|
|
708
701
|
* it via `apiKeyProvider`.
|
|
709
702
|
*
|
|
710
703
|
* The browser must NEVER see the `sk_` key — only the per-user session token.
|
|
711
704
|
*
|
|
712
705
|
* Pass `{ user: { id } }` for a full-authority end-user session (mints `ek_`,
|
|
713
|
-
* `
|
|
706
|
+
* `participantKind: 'user'` attribution, stored as `actor_kind` on the delta
|
|
707
|
+
* row), or `{ agent: { id }, can: { tasks:
|
|
714
708
|
* ['update'] } }` for a scoped agent session (mints `rk_`); `can` is typed
|
|
715
709
|
* against your schema's model names. Always authenticates with the original
|
|
716
710
|
* `sk_` — never the client's exchanged sync credential.
|
|
@@ -822,24 +816,6 @@ export type Ablo<S extends SchemaRecord> = {
|
|
|
822
816
|
* are schema-powered sugar over the same model write/read path.
|
|
823
817
|
*/
|
|
824
818
|
model<T = Record<string, unknown>>(name: string): ModelClient<T>;
|
|
825
|
-
/**
|
|
826
|
-
* Canonical multiplayer participant surface. Joins a structured app
|
|
827
|
-
* target, derives the transport scope internally, opens a scoped
|
|
828
|
-
* claim on the existing WebSocket, and returns target-bound presence
|
|
829
|
-
* + claim helpers.
|
|
830
|
-
*
|
|
831
|
-
* ```ts
|
|
832
|
-
* const participant = await ablo.participants.join({
|
|
833
|
-
* type: 'File',
|
|
834
|
-
* id: 'src/foo.ts',
|
|
835
|
-
* path: 'src/foo.ts',
|
|
836
|
-
* range: { startLine: 10, endLine: 40 },
|
|
837
|
-
* });
|
|
838
|
-
* participant.presence.editing();
|
|
839
|
-
* const claim = participant.claims.claim('rewrite imports');
|
|
840
|
-
* ```
|
|
841
|
-
*/
|
|
842
|
-
readonly participants: ParticipantManager;
|
|
843
819
|
/**
|
|
844
820
|
* Capture a context-staleness watermark over a set of entities.
|
|
845
821
|
* Returns a flat snapshot with `stamp` (thread into writes as
|
|
@@ -991,9 +967,6 @@ export declare namespace Ablo {
|
|
|
991
967
|
type ClaimLost = _Streams.ClaimLost;
|
|
992
968
|
type Snapshot<TSchema extends _SchemaTypes.Schema = _SchemaTypes.Schema, K extends keyof TSchema['models'] = keyof TSchema['models']> = _Streams.Snapshot<TSchema, K>;
|
|
993
969
|
namespace Auth {
|
|
994
|
-
type Principal = _Streams.Principal;
|
|
995
|
-
type Session = _Streams.SessionRef;
|
|
996
|
-
type Agent = _Streams.AgentRef;
|
|
997
970
|
type Actor = _Streams.ParticipantRef;
|
|
998
971
|
}
|
|
999
972
|
namespace Participant {
|