@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
@@ -44,13 +44,18 @@ objects by hand.
44
44
 
45
45
  ## Your Database
46
46
 
47
- Every schema model is backed by **your own database**. You expose a signed Data
48
- Source endpoint; Ablo coordinates each write and your app commits it to your
49
- Postgres. The SDK call shape is the same everywhere.
47
+ Every schema model is backed by **your own database**. The SDK call shape is the
48
+ same everywhere.
50
49
 
51
- Do not pass a database URL to `Ablo(...)`. Application and agent code use
52
- `ABLO_API_KEY`. If your database stays canonical, expose a signed Data Source
53
- endpoint from your app and keep the database credentials inside your app.
50
+ In this guide an app that already owns its backend and database keep
51
+ `DATABASE_URL` inside your app and expose a signed Data Source endpoint: Ablo
52
+ coordinates each write and your app commits it to your Postgres. Do not pass
53
+ `databaseUrl` to `Ablo(...)` here; application and agent code use `ABLO_API_KEY`.
54
+
55
+ If instead you want Ablo to connect to your Postgres directly, pass `databaseUrl`
56
+ (a live, server-only option) to `Ablo(...)`. That and the sandbox-only `apiKey`
57
+ shape are the other two start states — [Connect Your Database](./data-sources.md)
58
+ is the single source of truth for all three.
54
59
 
55
60
  ## Test With Sandboxes
56
61
 
@@ -94,12 +99,13 @@ import { defineSchema, model, z } from '@abloatai/ablo/schema';
94
99
  export const schema = defineSchema(
95
100
  {
96
101
  weatherReports: model({
97
- id: z.string(),
102
+ // Reserved fields (id, createdAt, updatedAt, organizationId, createdBy)
103
+ // are SDK-provided automatically — never declare them. Declare only your
104
+ // own fields.
98
105
  projectId: z.string(),
99
106
  location: z.string(),
100
107
  status: z.enum(['pending', 'ready']),
101
108
  assigneeId: z.string().nullable(),
102
- updatedAt: z.string(),
103
109
  }),
104
110
  },
105
111
  {
@@ -169,7 +175,7 @@ export const ablo = Ablo({
169
175
  Browser apps should use the React provider or a scoped session token, not a
170
176
  server API key in the bundle. Build the client first, then hand it to the
171
177
  provider — `AbloProvider` takes `{ client, userId?, onError?, fallback? }`, and
172
- nothing else (`schema`, `teamIds`, `scope`, and `authEndpoint` all live on the
178
+ nothing else (`schema`, `teamIds`, and `apiKey` all live on the
173
179
  client now).
174
180
 
175
181
  ```tsx
@@ -179,7 +185,10 @@ import { schema } from '@/ablo/schema';
179
185
 
180
186
  // The browser never holds the API key. The client mints a short-lived token
181
187
  // from your session route (see below) and refreshes it before expiry.
182
- export const ablo = Ablo({ schema, authEndpoint: '/api/ablo-session' });
188
+ export const ablo = Ablo({
189
+ schema,
190
+ apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
191
+ });
183
192
  ```
184
193
 
185
194
  ```tsx
package/docs/migration.md CHANGED
@@ -113,6 +113,11 @@ const ablo = Ablo({ schema, apiKey: process.env.ABLO_API_KEY, transport: 'http'
113
113
  await ablo.tasks.update({ id, data: { status: 'done' } });
114
114
  ```
115
115
 
116
+ > **Minting still needs the stateful client.** `sessions.create(...)` is not on
117
+ > the `transport: 'http'` surface. Keep a default-transport `server` client for
118
+ > minting short-lived credentials (see the 0.9.2 example below), and use the
119
+ > http client for the per-request reads and writes.
120
+
116
121
  ---
117
122
 
118
123
  ## 0.9.2 — `turn` / agent-`tasks` removed; `intents` deprecated
@@ -135,8 +140,10 @@ helper, and the agent/task type family (`Agent`, `AgentOptions`,
135
140
  ```diff
136
141
  - const turn = await engine.beginTurn();
137
142
  - await Ablo({ apiKey }).agent(agentId, opts).run(prompt, handler);
138
- + // Mint a scoped credential, then claim + write under it.
139
- + const { token } = await ablo.sessions.create({ agent: { id: agentId } });
143
+ + // Mint a scoped credential from a stateful (default-transport) server client —
144
+ + // sessions.create lives on the stateful client, not on transport: 'http'.
145
+ + const server = Ablo({ schema, apiKey: process.env.ABLO_API_KEY });
146
+ + const { token } = await server.sessions.create({ agent: { id: agentId } });
140
147
  + const agent = Ablo({ schema, apiKey: token });
141
148
  + await using claim = await agent.tasks.claim({ id });
142
149
  + await agent.tasks.update({ id, data: { status: 'done' }, wait: 'confirmed' });
@@ -277,7 +284,8 @@ One provider component now owns the full React lifecycle. `<SyncProvider>`,
277
284
  - <SyncProvider store={sync._store} organizationId={orgId}>
278
285
  - <AbloProvider ablo={ablo}>{children}</AbloProvider>
279
286
  - </SyncProvider>
280
- + <AbloProvider schema={schema} url={url} userId={userId} organizationId={orgId}>
287
+ + const ablo = Ablo({ schema, apiKey });
288
+ + <AbloProvider client={ablo}>
281
289
  + {children}
282
290
  + </AbloProvider>
283
291
  ```
@@ -57,6 +57,9 @@ export const schema = defineSchema({
57
57
  });
58
58
  ```
59
59
 
60
+ **Reserved fields** — `id`, `createdAt`, `updatedAt`, `organizationId`, and
61
+ `createdBy` are provided by the SDK automatically. Don't declare them in your
62
+ `model(...)` fields; declare only your own.
60
63
 
61
64
  The schema is registered once (init scaffolds `ablo/register.ts` for you), and
62
65
  every type is one parameter away — no `typeof schema` re-stating, anywhere:
@@ -210,8 +213,9 @@ and you write the usual way with `ablo.<model>.update({ id, data })`.
210
213
  Claims don't lock. If another writer holds the row, `claim` waits for them,
211
214
  re-reads the fresh row, then hands it to you — so two writers serialize instead
212
215
  of clobbering. Normal reads still work while the claim is held. If a server read
213
- should not return a row while someone else is mid-edit, pass `ifClaimed: 'wait'`
214
- to wait for the claim to clear, or `ifClaimed: 'fail'` to error out instead.
216
+ should not return a row while someone else is mid-edit, pass `ifClaimed: 'fail'`
217
+ to error out instead. Reads never block on a claim to wait for a row to free
218
+ up, `claim({ id })` it (the claim queues fairly behind the holder).
215
219
  Call `handle.release()` when your work is done.
216
220
 
217
221
  ```ts
package/docs/react.md CHANGED
@@ -31,7 +31,7 @@ import { schema } from '@/ablo/schema';
31
31
  // from your own server route (see Identity below).
32
32
  export const ablo = Ablo({
33
33
  schema,
34
- authEndpoint: '/api/ablo-session',
34
+ apiKey: () => fetch('/api/ablo-session').then((r) => r.text()),
35
35
  });
36
36
  ```
37
37
 
@@ -65,13 +65,13 @@ export function Providers({
65
65
 
66
66
  | Prop | Default | Purpose |
67
67
  | ----------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
68
- | `client` | — | **Required.** The `Ablo({ schema, authEndpoint })` instance. It carries the schema and connection config. |
68
+ | `client` | — | **Required.** The `Ablo({ schema, apiKey })` instance. It carries the schema and connection config. |
69
69
  | `userId` | resolved from auth | App participant id for app-owned fields and your `identityRoles`. Not the security boundary. |
70
70
  | `fallback` | neutral spinner | Rendered during the *first* bootstrap only. Pass a branded skeleton, `null`, or `'passthrough'`. |
71
71
  | `onError` | — | Engine / WebSocket / bootstrap errors. Wire to Sentry / Datadog. |
72
72
 
73
73
  Everything that used to be a provider prop — `schema`, `url`, `apiKey`,
74
- `teamIds`, `syncGroups`/`scope`, `persistence`, `bootstrapMode` — now lives on
74
+ `teamIds`, `syncGroups`, `persistence`, `bootstrapMode` — now lives on
75
75
  the `Ablo({ ... })` client you build before mounting the provider. Where the
76
76
  identity comes from, and why the API key never reaches the browser, is the whole
77
77
  of [Identity & Sync Groups](./identity.md) — read that if it isn't obvious how
@@ -49,6 +49,20 @@ The model key (`weatherReports`) becomes the client namespace
49
49
  contract. You should not create a parallel string-keyed write path for the same
50
50
  data.
51
51
 
52
+ ### Reserved fields
53
+
54
+ The SDK provides these on every row automatically — do **not** declare them in
55
+ your `model(...)` fields:
56
+
57
+ - `id`
58
+ - `createdAt`
59
+ - `updatedAt`
60
+ - `organizationId`
61
+ - `createdBy`
62
+
63
+ Declare only your own fields; the reserved ones are still present on the row and
64
+ readable, you just don't author them.
65
+
52
66
  ## Reads and writes
53
67
 
54
68
  Use async reads when the row may not be local:
@@ -88,12 +102,16 @@ the fresh row. Reads stay open; only acting on the row serializes.
88
102
 
89
103
  ## Storage boundary
90
104
 
91
- Every schema model is backed by your own database through a Data Source — Ablo
92
- coordinates each write and your app commits it to your Postgres.
105
+ Every schema model is backed by your own database. There are three start states,
106
+ all covered in [Connect Your Database](./data-sources.md) (the single source of
107
+ truth): the sandbox (`apiKey` only, no database), a direct connection string
108
+ (`databaseUrl` passed to `Ablo(...)`, a live, server-only option), or a signed
109
+ Data Source endpoint where your app keeps the database credential and commits
110
+ each write itself.
93
111
 
94
- Do not pass a database URL to `Ablo(...)`. Trusted runtimes use `ABLO_API_KEY`.
95
- Browser code goes through `<AbloProvider>` or a scoped session route, never a raw
96
- API key.
112
+ If your database stays canonical behind a Data Source endpoint, do not pass
113
+ `databaseUrl` to `Ablo(...)` trusted runtimes use `ABLO_API_KEY`. Browser code
114
+ goes through `<AbloProvider>` or a scoped session route, never a raw API key.
97
115
 
98
116
  ## Rules of thumb
99
117
 
package/llms-full.txt CHANGED
@@ -39,9 +39,9 @@ Use `snapshot(...)` and `readAt` when an agent write depends on state it already
39
39
  read. `onStale: 'reject'` prevents lost updates by rejecting if the target
40
40
  changed after the snapshot.
41
41
 
42
- Claims are live coordination signals, not database locks. Schema clients wait
43
- from the realtime claim stream. Schema-less HTTP clients must pass an explicit
44
- `claimedPollInterval` for `ifClaimed: 'wait'`; no hidden hard-coded polling.
42
+ Claims are live coordination signals, not database locks. Reads never block on a
43
+ claim to wait for a row to free up, `claim({ id })` it and the claim queues
44
+ fairly behind the current holder.
45
45
 
46
46
  Agent run bookkeeping is internal. Most users should not create run ledger
47
47
  records or scoped access credentials manually.
@@ -307,7 +307,10 @@ common agent path.
307
307
 
308
308
  ## Model
309
309
 
310
- Every model read returns state plus coordination metadata:
310
+ The read shape depends on whether the client has a local graph:
311
+
312
+ - The stateful `ablo.<model>.retrieve({ id })` returns the BARE row (`T | undefined`), and `list({ where })` returns `T[]`. Read coordination metadata (state watermark, active claims) separately via `ablo.snapshot(...)` and `ablo.<model>.claim.state({ id })`.
313
+ - The stateless HTTP / `.model(name)` `retrieve({ id })` returns a `ModelRead<T>` envelope because it has no local graph to carry the watermark; `list({ where })` still returns `T[]`.
311
314
 
312
315
  ```ts
313
316
  type ModelRead<T> = {
@@ -317,32 +320,31 @@ type ModelRead<T> = {
317
320
  };
318
321
  ```
319
322
 
320
- `stamp` is the state watermark. Pass it to writes as `readAt`.
323
+ `stamp` is the state watermark. Pass it to writes as `readAt` (from `ablo.snapshot(...)` on the stateful client, or from the envelope on the HTTP client).
321
324
 
322
325
  `claims` lists active work on the target. Reads are allowed while another participant is working. The caller decides whether to return, fail, or wait.
323
326
 
324
327
  ## Claimed Behavior
325
328
 
326
- Claimed behavior is explicit:
329
+ Claimed behavior is explicit, and there are only two policies:
327
330
 
328
- - `ifClaimed: 'return'` returns the current claims with the read.
331
+ - `ifClaimed: 'return'` (the default) returns the row plus the current claims with the read.
329
332
  - `ifClaimed: 'fail'` throws `AbloClaimedError`.
330
- - `ifClaimed: 'wait'` waits for matching claims to clear.
331
-
332
- Schema clients use the realtime claim stream for waits.
333
333
 
334
- Schema-less HTTP clients cannot know when a claim clears unless the caller opts into polling. When using `ifClaimed: 'wait'` over HTTP, provide `claimedPollInterval` and usually `claimedTimeout`.
334
+ Reads never block on a claim. To wait for a row to free up, `claim({ id })` it
335
+ the claim queues fairly behind the current holder and is granted when the row
336
+ frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
335
337
 
336
338
  ```ts
339
+ // Read a claimed row without blocking, surfacing who holds it:
337
340
  await api.model('reports').retrieve({
338
341
  id: 'report_stockholm',
339
- ifClaimed: 'wait',
340
- claimedPollInterval: 1_000,
341
- claimedTimeout: 30_000,
342
+ ifClaimed: 'return',
342
343
  });
343
- ```
344
344
 
345
- No hidden hard-coded claimed polling. `claimedTimeout` is a maximum wait, not the coordination mechanism.
345
+ // Or wait for it to free up by queueing your own claim:
346
+ await api.model('reports').claim({ id: 'report_stockholm' });
347
+ ```
346
348
 
347
349
  ## Write
348
350
 
package/llms.txt CHANGED
@@ -99,13 +99,13 @@ coordination until the app reports it through Data Source events.
99
99
  ## Claimed Behavior
100
100
 
101
101
  Reads never silently block. Schema reads stay open while a row is claimed.
102
- Server reads through `ablo.model(name)` can pass `ifClaimed: 'return'` to
103
- receive active claims, `ifClaimed: 'fail'` to throw `AbloClaimedError`, or
104
- `ifClaimed: 'wait'` to wait until the active claim clears.
102
+ Server reads through `ablo.model(name)` can pass `ifClaimed: 'return'` (the
103
+ default — returns the row plus active claim metadata) or `ifClaimed: 'fail'`
104
+ to throw `AbloClaimedError`.
105
105
 
106
- Schema clients learn when a claim clears by listening to the live claim stream, so they don't need to poll. Schema-less HTTP callers must provide an explicit `claimedPollInterval` when using `ifClaimed: 'wait'`; Ablo does not hide a hard-coded polling loop.
107
-
108
- Use `claimedTimeout` only as a maximum wait, not as the coordination mechanism.
106
+ Reads never block on a claim. To wait for a row to free up, `claim({ id })` it
107
+ the claim queues fairly behind the current holder and is granted when the row
108
+ frees. Use `ifClaimed: 'fail'` when you'd rather refuse to read a claimed row.
109
109
 
110
110
  ## Guarantees
111
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/ablo",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "The Collaboration Layer For AI Agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,10 +0,0 @@
1
- /**
2
- * Internal compatibility entrypoint for the stateless hosted protocol client.
3
- *
4
- * Use this build for serverless functions, scripts, and backends that want
5
- * model reads/writes and commits over HTTP without the realtime sync runtime.
6
- */
7
- export { createProtocolClient, createProtocolClient as Ablo, type AbloApi, type AbloApiClientOptions, type AbloApiClaims, type Capability, type CapabilityCreateOptions, type CapabilityParticipantKind, type CapabilityRecord, type CapabilityResource, type CapabilityRevocation, type CapabilityScope, } from '../client/ApiClient.js';
8
- export type { CommitCreateOptions, CommitOperationInput, CommitReceipt, CommitWait, ClaimCreateOptions, ClaimHandle, ClaimWaitOptions, ClaimedOptions, IfClaimedPolicy, ModelClient, ModelClaim, ModelMutationOptions, ModelReadOptions, ModelRead, ModelTarget, } from '../client/Ablo.js';
9
- import { createProtocolClient } from '../client/ApiClient.js';
10
- export default createProtocolClient;
package/dist/api/index.js DELETED
@@ -1,9 +0,0 @@
1
- /**
2
- * Internal compatibility entrypoint for the stateless hosted protocol client.
3
- *
4
- * Use this build for serverless functions, scripts, and backends that want
5
- * model reads/writes and commits over HTTP without the realtime sync runtime.
6
- */
7
- export { createProtocolClient, createProtocolClient as Ablo, } from '../client/ApiClient.js';
8
- import { createProtocolClient } from '../client/ApiClient.js';
9
- export default createProtocolClient;
@@ -1,44 +0,0 @@
1
- /**
2
- * Principal constructors — a thin typed façade over the raw
3
- * `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
4
- * the discriminated-union tags.
5
- *
6
- * ```ts
7
- * import Ablo, { session } from '@abloatai/ablo';
8
- *
9
- * const ablo = Ablo({ schema, apiKey });
10
- * const participant = await ablo.participants.join({
11
- * type: 'Matter',
12
- * id: 'deal-1',
13
- * });
14
- * ```
15
- *
16
- * Browser-human flows use `session(...)`. Agent-spawn-agent flows use
17
- * `agent(...)`, but those rarely appear in customer code because the
18
- * participant layer handles attenuation.
19
- *
20
- * These are pure — no I/O, no hidden state. If the shape ever grows a
21
- * required field (say, a scope hint for the restricted key), the helper
22
- * is the one place to flag migrations.
23
- */
24
- import type { AgentRef, SessionRef } from './types/streams.js';
25
- /**
26
- * Build a `SessionRef` from the identifiers your auth system already
27
- * holds. Typical inputs: the Better Auth session id, the user id, and
28
- * the organization the session is scoped to.
29
- */
30
- export declare function session(params: {
31
- id: string;
32
- userId: string;
33
- organizationId: string;
34
- }): SessionRef;
35
- /**
36
- * Build an `AgentRef` from an agent id + the capability token that
37
- * authenticates it. Rare in application code — the common path is
38
- * `participant.join(child)` where the parent's token is attenuated
39
- * automatically.
40
- */
41
- export declare function agent(params: {
42
- id: string;
43
- capabilityToken: string;
44
- }): AgentRef;
package/dist/principal.js DELETED
@@ -1,49 +0,0 @@
1
- /**
2
- * Principal constructors — a thin typed façade over the raw
3
- * `SessionRef` / `AgentRef` shapes so call sites don't have to memorize
4
- * the discriminated-union tags.
5
- *
6
- * ```ts
7
- * import Ablo, { session } from '@abloatai/ablo';
8
- *
9
- * const ablo = Ablo({ schema, apiKey });
10
- * const participant = await ablo.participants.join({
11
- * type: 'Matter',
12
- * id: 'deal-1',
13
- * });
14
- * ```
15
- *
16
- * Browser-human flows use `session(...)`. Agent-spawn-agent flows use
17
- * `agent(...)`, but those rarely appear in customer code because the
18
- * participant layer handles attenuation.
19
- *
20
- * These are pure — no I/O, no hidden state. If the shape ever grows a
21
- * required field (say, a scope hint for the restricted key), the helper
22
- * is the one place to flag migrations.
23
- */
24
- /**
25
- * Build a `SessionRef` from the identifiers your auth system already
26
- * holds. Typical inputs: the Better Auth session id, the user id, and
27
- * the organization the session is scoped to.
28
- */
29
- export function session(params) {
30
- return {
31
- kind: 'session',
32
- id: params.id,
33
- userId: params.userId,
34
- organizationId: params.organizationId,
35
- };
36
- }
37
- /**
38
- * Build an `AgentRef` from an agent id + the capability token that
39
- * authenticates it. Rare in application code — the common path is
40
- * `participant.join(child)` where the parent's token is attenuated
41
- * automatically.
42
- */
43
- export function agent(params) {
44
- return {
45
- kind: 'agent',
46
- id: params.id,
47
- capabilityToken: params.capabilityToken,
48
- };
49
- }
@@ -1,19 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- export interface SyncGroupProviderProps {
3
- /** The sync-group identifier — e.g., `matter:abc-123`, `deck:xyz`. */
4
- id: string;
5
- children: ReactNode;
6
- }
7
- export declare function SyncGroupProvider({ id, children }: SyncGroupProviderProps): import("react").JSX.Element;
8
- /**
9
- * Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
10
- * called outside one — sync-group awareness is mandatory by design,
11
- * so the error points the consumer at the provider instead of
12
- * returning undefined and letting downstream code silently miss scope.
13
- *
14
- * If a component legitimately renders both inside and outside a
15
- * group, structure the tree so the hook is only called on the
16
- * inside path (e.g., split into two components). Silent nulls are
17
- * never the right answer.
18
- */
19
- export declare function useSyncGroup(): string;
@@ -1,44 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useMemo } from 'react';
4
- import { AbloValidationError } from '../errors.js';
5
- /**
6
- * Narrow context for a per-entity sync-group scope. Maps directly onto
7
- * Liveblocks' `<RoomProvider id="...">`: wrap a subtree, and any hooks
8
- * inside can read `useSyncGroup()` to discover "which entity am I
9
- * scoped to?" without threading the ID through props.
10
- *
11
- * Typical IDs follow the multiplayer sync-group convention: `matter:<id>`,
12
- * `deck:<id>`, `project:<id>`. The ID is an opaque string — the
13
- * provider doesn't parse it.
14
- *
15
- * v0.3.0 scope: this is a thin passthrough. Future versions will
16
- * scope `useQuery` / `useOne` results to the group automatically.
17
- */
18
- const SyncGroupContext = createContext(null);
19
- export function SyncGroupProvider({ id, children }) {
20
- // Stabilize the context value so consumers memoized on it don't
21
- // re-render when the provider re-renders for unrelated reasons.
22
- const value = useMemo(() => id, [id]);
23
- return _jsx(SyncGroupContext.Provider, { value: value, children: children });
24
- }
25
- /**
26
- * Returns the ID of the nearest `<SyncGroupProvider>`. Throws if
27
- * called outside one — sync-group awareness is mandatory by design,
28
- * so the error points the consumer at the provider instead of
29
- * returning undefined and letting downstream code silently miss scope.
30
- *
31
- * If a component legitimately renders both inside and outside a
32
- * group, structure the tree so the hook is only called on the
33
- * inside path (e.g., split into two components). Silent nulls are
34
- * never the right answer.
35
- */
36
- export function useSyncGroup() {
37
- const id = useContext(SyncGroupContext);
38
- if (!id) {
39
- throw new AbloValidationError('useSyncGroup: no <SyncGroupProvider> mounted above this component. ' +
40
- 'Wrap your tree with <SyncGroupProvider id="matter:..."> from ' +
41
- '@abloatai/ablo/react.', { code: 'no_sync_group_provider' });
42
- }
43
- return id;
44
- }
@@ -1,29 +0,0 @@
1
- import type { ResolveClaims } from '../types/global.js';
2
- /**
3
- * Named-claim invoker, typed via `ResolveClaims[ClaimName]`.
4
- *
5
- * The consumer declares their claim vocabulary in the global:
6
- *
7
- * ```ts
8
- * declare module '@abloatai/ablo' {
9
- * interface Register {
10
- * Claims: {
11
- * editLayer: { slideId: string; layerId: string };
12
- * generateWithAI: { entityId: string; tool: string };
13
- * };
14
- * }
15
- * }
16
- * ```
17
- *
18
- * Then `useClaim('editLayer')` returns a function whose sole argument
19
- * is the `editLayer` claim shape — no runtime checks, purely compile-
20
- * time narrowing.
21
- *
22
- * The SDK doesn't own what happens next: the `beginClaim` function on
23
- * the React context (supplied via `SyncProvider`) is where the claim
24
- * claim turns into a network effect. A Node-backed consumer wires it
25
- * through `SyncAgent.beginClaim`; a browser-backed consumer may
26
- * broadcast it through their own WebSocket. This hook is pure sugar
27
- * that adds the typed name + claim narrowing.
28
- */
29
- export declare function useClaim<Name extends keyof ResolveClaims & string>(claimName: Name): (claim: ResolveClaims[Name]) => unknown;
@@ -1,42 +0,0 @@
1
- 'use client';
2
- import { useCallback } from 'react';
3
- import { useSyncContext } from './context.js';
4
- import { AbloValidationError } from '../errors.js';
5
- /**
6
- * Named-claim invoker, typed via `ResolveClaims[ClaimName]`.
7
- *
8
- * The consumer declares their claim vocabulary in the global:
9
- *
10
- * ```ts
11
- * declare module '@abloatai/ablo' {
12
- * interface Register {
13
- * Claims: {
14
- * editLayer: { slideId: string; layerId: string };
15
- * generateWithAI: { entityId: string; tool: string };
16
- * };
17
- * }
18
- * }
19
- * ```
20
- *
21
- * Then `useClaim('editLayer')` returns a function whose sole argument
22
- * is the `editLayer` claim shape — no runtime checks, purely compile-
23
- * time narrowing.
24
- *
25
- * The SDK doesn't own what happens next: the `beginClaim` function on
26
- * the React context (supplied via `SyncProvider`) is where the claim
27
- * claim turns into a network effect. A Node-backed consumer wires it
28
- * through `SyncAgent.beginClaim`; a browser-backed consumer may
29
- * broadcast it through their own WebSocket. This hook is pure sugar
30
- * that adds the typed name + claim narrowing.
31
- */
32
- export function useClaim(claimName) {
33
- const { beginClaim } = useSyncContext();
34
- return useCallback((claim) => {
35
- if (!beginClaim) {
36
- throw new AbloValidationError(`useClaim: no \`beginClaim\` wired into SyncProvider. Pass ` +
37
- `a \`beginClaim\` prop (typically bound to your transport) ` +
38
- `to enable claim invocations.`, { code: 'claim_not_wired' });
39
- }
40
- return beginClaim(claimName, claim);
41
- }, [beginClaim, claimName]);
42
- }
@@ -1,32 +0,0 @@
1
- import type { ResolvePresence } from '../types/global.js';
2
- /**
3
- * Read the consumer-supplied presence state with `ResolvePresence`d
4
- * typing — the shape the consumer declared in
5
- * `declare module '@abloatai/ablo' { interface Register { Presence: ... } }`.
6
- *
7
- * The SDK doesn't own a presence wire format. Consumers plug whatever
8
- * backs their cursors, status, or activity (a MobX store, a custom
9
- * WebSocket channel, `SyncAgent` in Node, a Zustand slice) via the
10
- * `presence` prop on `SyncProvider`. This hook returns it typed.
11
- *
12
- * ```ts
13
- * // apps/your-app/src/ablo-sync.d.ts
14
- * declare module '@abloatai/ablo' {
15
- * interface Register {
16
- * Presence: { cursor: { x: number; y: number } | null; status: 'away' | 'online' };
17
- * }
18
- * }
19
- *
20
- * // consumer's <SyncProvider> wiring
21
- * <SyncProvider store={store} organizationId={orgId} presence={presenceStore}>
22
- *
23
- * // any component
24
- * const presence = usePresence();
25
- * presence?.cursor?.x; // fully typed
26
- * ```
27
- *
28
- * Returns `undefined` when no provider-level presence source is wired —
29
- * consumers can narrow with a guard or configure a default in their
30
- * provider.
31
- */
32
- export declare function usePresence(): ResolvePresence | undefined;
@@ -1,41 +0,0 @@
1
- 'use client';
2
- import { useSyncContext } from './context.js';
3
- /**
4
- * Read the consumer-supplied presence state with `ResolvePresence`d
5
- * typing — the shape the consumer declared in
6
- * `declare module '@abloatai/ablo' { interface Register { Presence: ... } }`.
7
- *
8
- * The SDK doesn't own a presence wire format. Consumers plug whatever
9
- * backs their cursors, status, or activity (a MobX store, a custom
10
- * WebSocket channel, `SyncAgent` in Node, a Zustand slice) via the
11
- * `presence` prop on `SyncProvider`. This hook returns it typed.
12
- *
13
- * ```ts
14
- * // apps/your-app/src/ablo-sync.d.ts
15
- * declare module '@abloatai/ablo' {
16
- * interface Register {
17
- * Presence: { cursor: { x: number; y: number } | null; status: 'away' | 'online' };
18
- * }
19
- * }
20
- *
21
- * // consumer's <SyncProvider> wiring
22
- * <SyncProvider store={store} organizationId={orgId} presence={presenceStore}>
23
- *
24
- * // any component
25
- * const presence = usePresence();
26
- * presence?.cursor?.x; // fully typed
27
- * ```
28
- *
29
- * Returns `undefined` when no provider-level presence source is wired —
30
- * consumers can narrow with a guard or configure a default in their
31
- * provider.
32
- */
33
- export function usePresence() {
34
- const ctx = useSyncContext();
35
- // The runtime value is whatever the consumer passed to `SyncProvider`.
36
- // The type assertion reflects the consumer's declared global, which
37
- // the hook can't verify at runtime — but the consumer controls both
38
- // ends (the registration and the provider prop) so this is a
39
- // single-source-of-truth contract, not blind trust.
40
- return ctx.presence;
41
- }