@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +10 -2
  3. package/dist/Model.d.ts +39 -0
  4. package/dist/Model.js +68 -0
  5. package/dist/ai-sdk/claim-broadcast.d.ts +4 -3
  6. package/dist/ai-sdk/claim-broadcast.js +2 -2
  7. package/dist/ai-sdk/wrap.d.ts +5 -4
  8. package/dist/ai-sdk/wrap.js +3 -3
  9. package/dist/auth/credentialPolicy.d.ts +145 -0
  10. package/dist/auth/credentialPolicy.js +130 -0
  11. package/dist/cli.cjs +42 -7
  12. package/dist/client/Ablo.d.ts +64 -91
  13. package/dist/client/Ablo.js +43 -103
  14. package/dist/client/ApiClient.d.ts +10 -1
  15. package/dist/client/ApiClient.js +45 -22
  16. package/dist/client/auth.d.ts +12 -5
  17. package/dist/client/auth.js +2 -1
  18. package/dist/client/createModelProxy.d.ts +64 -17
  19. package/dist/client/createModelProxy.js +18 -12
  20. package/dist/client/httpClient.d.ts +17 -3
  21. package/dist/client/httpClient.js +1 -0
  22. package/dist/client/identity.js +134 -122
  23. package/dist/client/index.d.ts +1 -1
  24. package/dist/client/sessionMint.d.ts +15 -0
  25. package/dist/client/sessionMint.js +86 -0
  26. package/dist/coordination/schema.d.ts +1 -1
  27. package/dist/coordination/schema.js +3 -1
  28. package/dist/errorCodes.d.ts +2 -0
  29. package/dist/errorCodes.js +2 -0
  30. package/dist/errors.d.ts +6 -3
  31. package/dist/errors.js +9 -3
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.js +4 -7
  34. package/dist/mutators/RecordingTransaction.js +14 -42
  35. package/dist/react/AbloProvider.d.ts +12 -13
  36. package/dist/react/AbloProvider.js +10 -10
  37. package/dist/react/context.d.ts +10 -45
  38. package/dist/react/context.js +12 -17
  39. package/dist/react/index.d.ts +8 -10
  40. package/dist/react/index.js +8 -11
  41. package/dist/react/useMutators.js +3 -2
  42. package/dist/react/useSyncStatus.d.ts +1 -1
  43. package/dist/react/useUndoScope.js +3 -2
  44. package/dist/realtime/index.d.ts +1 -1
  45. package/dist/schema/generate.js +1 -2
  46. package/dist/schema/model.d.ts +10 -3
  47. package/dist/schema/schema.d.ts +13 -2
  48. package/dist/schema/schema.js +26 -0
  49. package/dist/surface.d.ts +29 -0
  50. package/dist/surface.js +60 -0
  51. package/dist/sync/ConnectionManager.d.ts +16 -5
  52. package/dist/sync/ConnectionManager.js +42 -7
  53. package/dist/sync/createClaimStream.js +5 -4
  54. package/dist/sync/participants.js +1 -1
  55. package/dist/transactions/TransactionQueue.d.ts +0 -11
  56. package/dist/transactions/TransactionQueue.js +12 -56
  57. package/dist/types/global.d.ts +3 -0
  58. package/dist/types/streams.d.ts +17 -29
  59. package/dist/utils/mobx-setup.js +1 -0
  60. package/docs/api-keys.md +49 -0
  61. package/docs/api.md +3 -2
  62. package/docs/client-behavior.md +1 -0
  63. package/docs/coordination.md +75 -21
  64. package/docs/examples/existing-python-backend.md +9 -5
  65. package/docs/examples/scoped-agent.md +1 -1
  66. package/docs/guarantees.md +4 -3
  67. package/docs/identity.md +89 -82
  68. package/docs/integration-guide.md +19 -10
  69. package/docs/migration.md +11 -3
  70. package/docs/quickstart.md +6 -2
  71. package/docs/react.md +3 -3
  72. package/docs/schema-contract.md +23 -5
  73. package/llms-full.txt +18 -16
  74. package/llms.txt +6 -6
  75. package/package.json +1 -1
  76. package/dist/api/index.d.ts +0 -10
  77. package/dist/api/index.js +0 -9
  78. package/dist/principal.d.ts +0 -44
  79. package/dist/principal.js +0 -49
  80. package/dist/react/SyncGroupProvider.d.ts +0 -19
  81. package/dist/react/SyncGroupProvider.js +0 -44
  82. package/dist/react/useClaim.d.ts +0 -29
  83. package/dist/react/useClaim.js +0 -42
  84. package/dist/react/usePresence.d.ts +0 -32
  85. 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
- action: import_zod3.z.string(),
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?.startsWith("rk_")) {
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.startsWith("rk_")) {
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 (!apiKey.startsWith("sk_")) {
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. \`authEndpoint\` points at the route below,
282926
- // which mints a short-lived session token (already scoped to the org + user).
282927
- const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
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>;
@@ -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 or an async `() => Promise<string>` resolver if you
81
- * rotate keys out-of-band (resolved at bootstrap).
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
- * Browser apps that mint a SHORT-LIVED per-user key (`ek_`) from a login can't
84
- * ship a secret those use {@link getToken} (or {@link authEndpoint}) instead,
85
- * which the client refreshes for you. That's the only case that isn't "just
86
- * `apiKey`".
87
- */
88
- apiKey?: string | ApiKeySetter | null | undefined;
89
- /**
90
- * Opt-in for the SHORT-LIVED per-user browser case: an async resolver for a
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
- * Contract: resolve a token, resolve `null` when the login itself is gone
98
- * (terminal → sign out), or THROW on a transient failure ( back off, never
99
- * sign out). Leave unset for the static-`apiKey` path.
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
- getToken?: (() => Promise<string | null>) | undefined;
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
- * @deprecated Server derives sync groups from the apiKey's scope.
357
- * Required today as a runtime holdover; removed once Phase 3 ships.
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 { ModelCountOptions, ModelListOptions, ModelListScope, ModelLoadOptions, ModelRetrieveParams, ModelCreateParams, ModelUpdateParams, ModelDeleteParams, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ClaimHandle, ModelOperations, } from './createModelProxy.js';
389
- import type { ModelOperations, ClaimOptions, ClaimParams, ClaimLookupParams, ClaimReorderParams, ModelLoadOptions } from './createModelProxy.js';
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' | 'wait' | 'fail';
382
+ export type IfClaimedPolicy = 'return' | 'fail';
398
383
  export interface ClaimedOptions {
399
384
  /**
400
- * What to do when another participant has claimed the target. `return`
401
- * includes active claim metadata in the response, `wait` resolves after the
402
- * claim clears, and `fail` throws `AbloClaimedError`.
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
- readonly action: string;
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?: ModelLoadOptions<T>): Promise<T[]>;
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.org()/user()/of()` from
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.org()/user()/of()` from
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
- * `<AbloProvider>`'s `getToken` refresh loop calls this. Reuses the same
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 `getToken` refresh loop), falling back to a configured API
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 `getToken` contract: resolve a token, resolve `null` when
689
- * the login itself is gone (→ sign out), or THROW on a transient failure (→
690
- * back off, never sign out). `<AbloProvider>` wires this from its
691
- * `getToken`/`authEndpoint`. Safe to call before `ready()`.
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 an authEndpoint the client
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
- * `actor_kind: 'user'` attribution), or `{ agent: { id }, can: { tasks:
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 {